###
# Copyright (c) 2002-2004, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the author of this software nor the name of
#     contributors to this software may be used to endorse or promote products
#     derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

"""
Handles configuration of the bot while it's running.
"""

import supybot

__revision__ = "$Id: Config.py,v 1.63 2004/12/18 14:00:27 jemfinch Exp $"
__author__ = supybot.authors.jemfinch

import os
import getopt
import signal

import supybot.log as log
import supybot.conf as conf
import supybot.utils as utils
import supybot.world as world
import supybot.ircdb as ircdb
from supybot.commands import *
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.callbacks as callbacks

###
# Now, to setup the registry.
###

def getWrapper(name):
    parts = registry.split(name)
    if not parts or parts[0] not in ('supybot', 'users'):
        raise InvalidRegistryName, name
    group = getattr(conf, parts.pop(0))
    while parts:
        try:
            group = group.get(parts.pop(0))
        # We'll catch registry.InvalidRegistryName and re-raise it here so
        # that we have a useful error message for the user.
        except (registry.NonExistentRegistryEntry,
                registry.InvalidRegistryName):
            raise registry.InvalidRegistryName, name
    return group

def getCapability(name):
    capability = 'owner' # Default to requiring the owner capability.
    parts = registry.split(name)
    while parts:
        part = parts.pop()
        if ircutils.isChannel(part):
            # If a registry value has a channel in it, it requires a channel.op
            # capability, or so we assume.  We'll see if we're proven wrong.
            capability = ircdb.makeChannelCapability(part, 'op')
        ### Do more later, for specific capabilities/sections.
    return capability

def _reload():
    ircdb.users.reload()
    ircdb.channels.reload()
    registry.open(world.registryFilename)

def _hupHandler(sig, frame):
    log.info('Received SIGHUP, reloading configuration.')
    _reload()

if os.name == 'posix':
    signal.signal(signal.SIGHUP, _hupHandler)


def getConfigVar(irc, msg, args, state):
    name = args[0]
    if name.startswith('conf.'):
        name = name[5:]
    if not name.startswith('supybot') and not name.startswith('users'):
        name = 'supybot.' + name
    try:
        group = getWrapper(name)
        state.args.append(group)
        del args[0]
    except registry.InvalidRegistryName, e:
        irc.errorInvalid('configuration variable', str(e))
addConverter('configVar', getConfigVar)

class Config(callbacks.Privmsg):
    def callCommand(self, name, irc, msg, *L, **kwargs):
        try:
            super(Config, self).callCommand(name, irc, msg, *L, **kwargs)
        except registry.InvalidRegistryValue, e:
            irc.error(str(e))

    def _list(self, group):
        L = []
        for (vname, v) in group._children.iteritems():
            if hasattr(group, 'channelValue') and group.channelValue and \
               ircutils.isChannel(vname) and not v._children:
                continue
            if hasattr(v, 'channelValue') and v.channelValue:
                vname = '#' + vname
            if v._added and not all(ircutils.isChannel, v._added):
                vname = '@' + vname
            L.append(vname)
        utils.sortBy(str.lower, L)
        return L

    def list(self, irc, msg, args, group):
        """<group>

        Returns the configuration variables available under the given
        configuration <group>.  If a variable has values under it, it is
        preceded by an '@' sign.  If a variable is a 'ChannelValue', that is,
        it can be separately configured for each channel using the 'channel'
        command in this plugin, it is preceded by an '#' sign.
        """
        L = self._list(group)
        if L:
            irc.reply(utils.commaAndify(L))
        else:
            irc.error('There don\'t seem to be any values in %s.' % group._name)
    list = wrap(list, ['configVar'])

    def search(self, irc, msg, args, word):
        """<word>

        Searches for <word> in the current configuration variables.
        """
        L = []
        for (name, _) in conf.supybot.getValues(getChildren=True):
            if word in name.lower():
                possibleChannel = registry.split(name)[-1]
                if not ircutils.isChannel(possibleChannel):
                    L.append(name)
        if L:
            irc.reply(utils.commaAndify(L))
        else:
            irc.reply('There were no matching configuration variables.')
    search = wrap(search, ['lowered']) # XXX compose with withoutSpaces?

    def _getValue(self, irc, msg, group):
        value = str(group) or ' '
        if hasattr(group, 'value'):
            if not group._private:
                irc.reply(value)
            else:
                capability = getCapability(group._name)
                if ircdb.checkCapability(msg.prefix, capability):
                    irc.reply(value, private=True)
                else:
                    irc.errorNoCapability(capability)
        else:
            irc.error('That registry variable has no value.  Use the list '
                      'command in this plugin to see what variables are '
                      'available in this group.')

    def _setValue(self, irc, msg, group, value):
        capability = getCapability(group._name)
        if ircdb.checkCapability(msg.prefix, capability):
            # I think callCommand catches exceptions here.  Should it?
            group.set(value)
            irc.replySuccess()
        else:
            irc.errorNoCapability(capability)
        
    def channel(self, irc, msg, args, channel, group, value):
        """[<channel>] <name> [<value>]

        If <value> is given, sets the channel configuration variable for <name>
        to <value> for <channel>.  Otherwise, returns the current channel
        configuration value of <name>.  <channel> is only necessary if the
        message isn't sent in the channel itself."""
        if not group.channelValue:
            irc.error('That configuration variable is not a channel-specific '
                      'configuration variable.')
            return
        group = group.get(channel)
        if value is not None:
            self._setValue(irc, msg, group, value)
        else:
            self._getValue(irc, msg, group)
    channel = wrap(channel, ['channel', 'configVar', additional('text')])

    def config(self, irc, msg, args, group, value):
        """<name> [<value>]

        If <value> is given, sets the value of <name> to <value>.  Otherwise,
        returns the current value of <name>.  You may omit the leading
        "supybot." in the name if you so choose.
        """
        if value is not None:
            self._setValue(irc, msg, group, value)
        else:
            self._getValue(irc, msg, group)
    config = wrap(config, ['configVar', additional('text')])

    def help(self, irc, msg, args, group):
        """<name>

        Returns the description of the configuration variable <name>.
        """
        if hasattr(group, '_help'):
            s = group.help()
            if s:
                if hasattr(group, 'value') and not group._private:
                    s += '  (Current value: %s)' % group
                irc.reply(s)
            else:
                irc.reply('That configuration group exists, but seems to have '
                          'no help.  Try "config list %s" to see if it has '
                          'any children values.')
        else:
            irc.error('%s has no help.' % name)
    help = wrap(help, ['configVar'])

    def default(self, irc, msg, args, group):
        """<name>

        Returns the default value of the configuration variable <name>.
        """
        v = group.__class__(group._default, '')
        irc.reply(str(v))
    default = wrap(default, ['configVar'])

    def reload(self, irc, msg, args):
        """takes no arguments

        Reloads the various configuration files (user database, channel
        database, registry, etc.).
        """
        _reload() # This was factored out for SIGHUP handling.
        irc.replySuccess()
    reload = wrap(reload, [('checkCapability', 'owner')])

    def export(self, irc, msg, args, filename):
        """<filename>

        Exports the public variables of your configuration to <filename>.
        If you want to show someone your configuration file, but you don't
        want that person to be able to see things like passwords, etc., this
        command will export a "sanitized" configuration file suitable for
        showing publicly.
        """
        registry.close(conf.supybot, filename, private=False)
        irc.replySuccess()
    export = wrap(export, [('checkCapability', 'owner'), 'filename'])


Class = Config

# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
