###
# Copyright (c) 2008, Hans-Christian Esperer
# 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.

###

import supybot.utils as utils
from supybot.commands import *
from cPickle import dumps, loads, dump, load
import supybot.plugins as plugins
import supybot.world as world
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
import supybot.conf as conf
from supybot.ircmsgs import privmsg, topic, IrcMsg

import threading
import socket

from time import time, localtime, mktime, ctime, sleep, strptime
from pprint import pformat
import os
import sys
import weakref
import random
import re
from sha import sha

MAX_TIMEOUT = 60
MAX_TIME_PLAYING = 600 # Tracks are no longer than 600 minutes


class EOFException(Exception): pass
class PermissionException(EOFException): pass

def ParseParam(ptp):
    B64MARKER = '_B64_'
    if ptp.startswith(B64MARKER):
        try: return ptp[len(B64MARKER):].decode('base64')
        except: return ptp
    else: return ptp

class LineReader:
    buf = ''
    CANCELAT = 8192 # max line length
    def __init__(self, socket):
        self.s = socket
    def readline(self):
        while True:
            pos = self.buf.find("\n")
            if pos != -1:
                line, self.buf = self.buf[:pos], self.buf[pos + 1:]
                return line
            if len(self.buf) > self.CANCELAT:
                # cause a panic
                try: self.s.close()
                except: pass
                return ''
            frag = self.s.recv(8192)
            if frag == None: raise EOFException()
            if len(frag) == 0: raise EOFException()
            frag = frag.replace("\r", "")
            if len(frag) == 0:
                buf, self.buf = self.buf, ''
                return buf # EOF
            self.buf = self.buf + frag

class SockHandler(threading.Thread):
    def __init__(self, socket, address, irc, plugin, commandlock):
        threading.Thread.__init__(self)
        self.s = socket
        self.address = address
        self.irc = irc
        self.plugin = plugin
        self.commandlock = commandlock
        self.authinfo = None
        self.authed = False
        self.dostop = False
    def FCT_getauth(self, parms):
        if self.authinfo != None:
            self.s.sendall('201 auth already sent\n')
            return
        if len(parms) < 1:
            self.s.sendall('340 getauth NONCE\n')
            return
        nonce = parms[0]
        if len(nonce) < 8:
            self.s.sendall('341 noe\n')
            return
        authcode = sha('%d' % random.randrange(1 << 31)).hexdigest()
        self.authinfo = (authcode, nonce)
        self.s.sendall('200 %s\n' % authcode)
    def FCT_auth(self, parms):
        if self.authinfo == None:
            self.s.sendall('503 noe\n')
            return
        if len(parms) < 1:
            self.s.sendall('340 auth CRESPONSE\n')
            return
        SHA_plaintext = '%s%s%s' % (self.authinfo[1], self.authinfo[0], "demiurge")
        hcode = sha(SHA_plaintext).hexdigest()
        if hcode != parms[0]:
            self.s.sendall('109 auth\n')
            return
        self.s.sendall('200 authenticated\n')
        self.authed = True
    def FCT_activate(self, parms):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        try: [album, title] = parms
        except:
            self.s.sendall('340 add ALBUM, TITLE\n')
            return
        if time() > self.plugin.expnumberplaying:
            self.plugin.numberplaying = 0
        self.plugin.playing = {"album": album, "title": title}
        self.plugin.DoActivate()
        self.plugin.numberplaying = self.plugin.numberplaying + 1
        self.plugin.expnumberplaying = time() + MAX_TIME_PLAYING
        self.s.sendall("200 track queued\n")
    def FCT_gettopic(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        self.s.sendall('200 %s\n' % self.irc.state.channels[self.plugin.sendChannel].topic)
    def FCT_getops(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        self.s.sendall('200 %s\n' % " ".join(self.irc.state.channels[self.plugin.sendChannel].ops))
    def FCT_getusers(self, params):
        if not self.authed: self.s.sendall('503 auth first\n')
        self.s.sendall('200 %s\n' % " ".join(self.irc.state.channels[self.plugin.sendChannel].users))
    def FCT_ircquote(self, params):
        if not self.authed:
            try:
                self.s.sendall('503 auth first\n')
                exit(0)
            except: pass
        self.plugin.irc.queueMsg(IrcMsg(params[0]))
    def FCT_finish(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        if self.plugin.playing == None:
            self.s.sendall('201 no track playing ATM\n')
            return
        if time() > self.plugin.expnumberplaying:
            self.plugin.numberplaying = 0
        if self.plugin.numberplaying <= 1:
            self.plugin.DoFinish()
            self.s.sendall('200 track is finished\n')
        else: self.s.sendall('200 track is finished, but another is still playing (%d)\n' % self.plugin.numberplaying)
        if self.plugin.numberplaying > 0:
            self.plugin.numberplaying = self.plugin.numberplaying - 1
    def FCT_ping(self, params):
        self.s.sendall('200 pong\n')
    def FCT_settopic(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        t_un = ''
        if len(params) < 1:
            tmsg = topic(self.plugin.sendChannel, ' ')
            t_un = 'un'
        else:
            mytopic = " | ".join(params)
            tmsg = topic(self.plugin.sendChannel, mytopic)
        self.plugin.irc.queueMsg(tmsg)
        self.s.sendall('200 topic %sset\n' % t_un)
    def FCT_say(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
        mymsg = ('[%s] ' % self.address[0]) + (' '.join(params))
        tmsg = privmsg(self.plugin.sendChannel, mymsg)
        self.plugin.irc.queueMsg(tmsg)
        self.s.sendall('200 privmsg sent\n')
    def FCT_show(self, params):
        if not self.authed:
            self.s.sendall('503 auth first\n')
            return
        pass
    def FCT_clear(self, params):
        if not self.authed: self.s.sendall('503 auth first\n')
    def FCT_addannounce(self, params):
        params = params[0].split(" ")
        self.plugin.sa.additem(eval(params[0]), " ".join(params[1:]))
        self.s.sendall("200 yo, bassd\n")
    def FCT_quit(self, parms):
        self.s.sendall('400 Auf Wiedersehen!\n')
        self.dostop = True
        try: self.s.close()
        except: pass
    def FCT_get(self, parms):
        for msg in self.plugin.sa.messages:
            if msg[0] == eval(parms[0]): self.s.sendall('200 %s\n' % msg[1])
    def stop(self):
        self.dostop = True
        try: self.s.close()
        except: pass
    def run(self):
        s = self.s
        s.settimeout(MAX_TIMEOUT)
        lr = LineReader(s)
        functions = self.__class__.__dict__
        try:
            while not self.dostop:
                try: line = lr.readline()
                except socket.timeout:
                    self.dostop = True
                    try: self.s.sendall('100 timeout\n')
                    except: pass
                    try: self.s.close()
                    except: pass
                    break
                if line.startswith("GET "):
                    lr.readline()
                    WWW(line).DoWWW(self)
                    self.stop()
                line = line.split(' ', 1)
                if len(line) < 1:
                    continue
                command = line[0]
                if len(line) > 1:
                    parms = line[1].split(',')
                    parms = [ParseParam(i) for i in parms]
                else: parms = []
                try: function = functions['FCT_%s' % command]
                except:
                    s.sendall('302 function does not exist\n')
                    continue
                try:
                    try:
                        self.commandlock.acquire_lock() # this lock is per playlist plugin instance
                        res = function.__call__(self, parms)
                    finally: self.commandlock.release_lock()
                except Exception, e:
                    s.sendall('303 function error: %s\n' % repr(type(e)))
                    continue
        except Exception, e:
            try: s.sendall('100 terminating: %s\n' % repr(type(e)))
            except: pass
            try: s.close()
            except: pass
            print 'done.'

class ConnectionLimitExceeded(threading.Thread):
    def __init__(self, socket, address):
        threading.Thread.__init__(self)
        self.s = socket
        self.a = address
    def run(self):
        self.s.settimeout(MAX_TIMEOUT)
        try: self.s.sendall('301 Connection limit from this IP exceeded\n')
        except Exception, e: print 'ConnectionLimitExceeded: %s' % repr(e)
        try: self.s.close()
        except Exception, e: print 'ConnectionLimitExceeded: %s' % repr(e)

def Ctnr():
    for i in range(256): yield i
SesGen = Ctnr()
class WWW():
    STATICS = {'/':
               """<html><head><title>Studentensekretariat und Radio</title></head>
                <body><h1>Begr&uuml;&szlig;ungsseite</h1>
                <p>Hallo %(stud)s, <br />
                ich freue mich, Sie bei uns willkommen heissen zu duerfen!
                <p>Bitte beachten Sie, dass unsere Webpr&auml;senz weitestgehend auf Englisch ist.
                <p>F&uuml;r die Startseite konnten wir leider noch keinen &Uuml;bersetzer finden, Entschuldigung!!
                <p>Wir haben ein reichhaltiges Angebot auf unseren <u>&uuml;bersichtlichen</u> Webseiten!
                <p>Sie k&ouml;nnen unsere Studierendenberatung im IRC im da-op3n-net unter Raute studentenwerk finden. Ausserdem nutzen Sie unseren Frage- und Antwortdienst, indem Sie <a href="/cgi-bin/say">hier</a> <u>auf diesen <b>link</b></u> klicken.
                <p>Nachrichten von allgemeinem Interesse k&ouml;nnen Sie auf unserem <a href="/cgi-bin/announce">Mitteilungsbrett</a> hinterlassen. Dankesch&ouml;n!
                """}
    BRICKS = {'stud': 'liebe studierende Menschen'}
    def __init__(self, line):
        self.line = line
        self.re = re.compile("GET ([^ \?]*)(\?.+)? HTTP/1.[0-9]?")
        self.lm = self.re.search(self.line)
        [self.req, self.parms] = self.lm.groups()
    def DoWWW(self, woot):
        def makesessionid(): return SesGen.next()
        ldict = {}
        for key in self.BRICKS: ldict[key] = self.BRICKS[key]
        try:
            for ke, va in [tuple(i.split('=')) for i in self.parms[1:].split("&")]: ldict[ke] = va
        except: pass
        if not 'sessionid' in ldict:
            ldict['sessionid'] = makesessionid()
            ldict['session'] = {}
            woot.plugin.sesss[ldict['sessionid']] = ldict['session']
        else:
            ldict['session'] = woot.plugin.sesss[eval(ldict['sessionid'])]

        headers = {
            'Connection': 'close',
            'Content-type': 'text/html',
            'Date': os.popen('date').read()[:-1],
            'Server': 'da op3n studServ 0.1 python'
        }
        woot.s.sendall("HTTP/1.1 200 OK\n\r%s\n\r\n\r" % ("\n\r".join(["%s: %s" % (k, headers[k]) for k in headers])))
        if self.req.startswith('/cgi-bin/'): self.DoCGI(self.req[9:], woot, ldict)
        elif self.req in self.STATICS: woot.s.sendall(self.STATICS[self.req] % ldict)
        else: woot.s.sendall("<html><body><p>unknown request type</body></html>\n\r")
    def DoCGI(self, whot, woot, ldict):
        try: whot, tohw = tuple(whot.split("/"))
        except: tohw = 'CGI_main'
        try:
            if '.' not in sys.path: sys.path.append('.')
            try: baz = __builtins__['__import__'].__call__(whot)
            except: baz = __builtins__.__dict__['__import__'].__call__(whot)
            try: baz = baz.__dict__[tohw]
            except: baz = baz[tohw]
            baz.__call__(self, ldict, woot, ldict['sessionid'], ldict['session'])
        except Exception, e: woot.s.sendall("<html><body><p>Internal server error</body></html>\n\r")
        


class SockListener(threading.Thread):
    def __init__(self, address, irc, plugin):
        threading.Thread.__init__(self)
        self.address = address
        self.irc = irc
        self.plugin = plugin
        self.dostop = False
        self.commandlock = threading.Lock()
        self.listeners = weakref.WeakValueDictionary()
    def __del__(self):
        print 'Connection handler for %s died.\n' % (self.address)
        for handler in self.listeners:
            print "SockListener: Stopping %s" % repr(handler)
            self.listeners[handler].stop()
    def stop(self):
        self.dostop = True
        try: self.s.close()
        except: pass
    def run(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s = s
        while not self.dostop:
            try: s.bind(self.address)
            except:
                print "SockListener: Error: cannot bind to %s" % repr(self.address)
                self.address = (self.address[0], self.address[1] + 1)
                sleep(2)
                continue
            s.listen(2) # backlog: 2 entries
            print "Listening on %s" % repr(self.address)
            while not self.dostop:
                try: cs, a = s.accept()
                except: continue
                if a[0] in self.listeners:
                    print 'SockListener: %s is in self.listeners' % repr(a[0])
                    ConnectionLimitExceeded(cs, a).start()
                    continue
                print "SockListener: Accepted connection from %s" % repr(a)
                handler = SockHandler(cs, a, self.irc, self.plugin, self.commandlock)
                handler.setDaemon(True)
                self.listeners[a[0]] = handler
                handler.start()
                handler = None # so that garbage collection works properly -- we depend on that!
        try:
            s.close()
            print 'SockListener: Main socket successfully closed'
        except: pass
        s = None

class ScheduledAnnouncer(threading.Thread):
    SLEEP_TIME = 60

    def __init__(self, channel, irc):
        threading.Thread.__init__(self)
        self.channel = channel
        self.dorun = True
        self.irc = irc
        self.messages = []
        self.lock = threading.Lock()
    def run(self):
        while self.dorun:
            sleep(self.SLEEP_TIME)
            try:
                self.lock.acquire_lock()
                try: message = self.messages[0]
                except: continue
                try: atime, msg = message
                except:
                    print "Fatal error! Here's something terribly wrong. Dumping"
                    print "the structure..."
                    print repr(self.messages)
                    print "===== self.messages = [] ====="
                    self.messages = []
                    continue
                if atime < time():
                    tmsg = topic(self.channel, msg)
                    self.irc.queueMsg(tmsg)
                    del self.messages[0]
            finally: self.lock.release_lock()
    def stop(self): self.dorun = False
    def additem(self, atime, message):
        self.lock.acquire_lock()
        self.messages.append((atime, message))
        self.messages.sort()
        self.lock.release_lock()
    def remove(self, mid):
        try:
            self.lock.acquire_lock()
            del self.messages[mid]
            self.messages.sort()
        finally: self.lock.release_lock()
    def clear(self):
        try:
            self.lock.acquire_lock()
            self.messages = []
        finally: self.lock.release_lock()
    def dump(self, f):
        try:
            self.lock.acquire_lock()
            dump(self.messages, f)
        except: self.lock.release_lock()
    def load(self, f):
        try:
            self.lock.acquire_lock()
            messages = load(f)
            self.messages = messages
        finally: self.lock.release_lock()

class Playlist(callbacks.Plugin):
    """HC's  Radio playlist plugin"""
    threaded = True

    def __init__(self, irc):
        self.__parent = super(Playlist, self)
        self.__parent.__init__(irc)
        self.flusher = self.flush
        self.irc = irc
        # manually saved/loaded stuff
        self.sendChannel = "#studentenradio"
        self.sendMsg = " | now playing: "
        self.titleFormat = "%(title)s from %(album)s"
        self.nextSendung = "Details for the next show can be found at http://www.datamining-online.de/search?q=studentenradio"
        self.miscStuff = ["studentenradio", "fm 103,4 MHz"]
        self.msgSeparator = " | "
        self.feedbackMsg = "The show is over; send your feedback to this channel"

        self.pl = []
        self.feedbackAnnounced = False
        self.sl = SockListener(('0.0.0.0', 1986), irc, self)
        self.sl.setDaemon(True)
        self.sl.start()
        self.sa = ScheduledAnnouncer(self.sendChannel, irc)
        self.sa.setDaemon(True)
        self.sa.start()
        self.numberplaying = 0
        self.expnumberplaying = 0
        self.saved = True
        self.logfile = None
        self.playing = None
        self.sesss = {}
        self.lastanswer = []
        self.topicAnnounced = False
        world.flushers.append(self.flusher)
        try: self.LoadSettings()
        except Exception, e:
            sys.stderr.write("Couldn't load settings; using defaults. (%s)" % e)
            self.saved = False

    def die(self):
        try: self.sl.stop()
        except Exception, e: print 'Stopping SockListener: %s' % e
        try: self.sa.stop()
        except Exception, e: print 'Stopping ScheduledAnnouncer: %s' % e
        world.flushers = [x for x in world.flushers if x is not self.flusher]

    def Checkpriv(self, irc, msg, channel):
        isOK = True
        if not self.CheckRightChannel(irc, channel):
            isOK = False
        if channel not in irc.state.channels:
            irc.error("No.")
            return False
        if msg.nick not in irc.state.channels[channel].ops:
            if isOK:
                try:
                    alch = open(msg.nick, 'r').read()
                    if alch.find(channel) != -1: return True
                    irc.error("You're allowed in the following channels, but not in the %s channel" % channel)
                    for line in alch.split("\n"): irc.error("allowed in %s" % line)
                except: irc.error("You can't do that thing, when you don't have that swing. (You're not channel operator)")
            else:
                irc.error("In addition to that, you don't have that swing, that you need to do that thing (You're not channel operator)")
            return False
        return True

    def DataDir(self):
        dataDir = conf.supybot.directories.data.dirize(self.name())
        if not os.path.exists(dataDir): os.makedirs(dataDir)
        return dataDir

    def CheckRightChannel(self, irc, channel):
        if channel != '#studentenradio-intern':
            for line in """This command may only be issued in #c-radar-intern.
If you want to issue this command in a private query with the bot,
you may do so, as long as:
  a) you specify #studentenradio-intern as first parameter to the command
  b) you are in the channel #studentenradio-intern and
  c) you are chanop of #studentenradio-intern.
If you've got additional questions, mail hc@hcesperer.org""".split("\n"): irc.error(line)
            return False
        return True

    def SaveSettings(self):
        if self.saved: return
        dd = self.DataDir() + "/globalvars.pickle"
        f = open(dd + "_tmp", 'w')
        dump(self.sendChannel, f)
        dump(self.sendMsg, f)
        dump(self.titleFormat, f)
        dump(self.nextSendung, f)
        dump(self.miscStuff, f)
        dump(self.msgSeparator, f)
        dump(self.pl, f)
        self.sa.dump(f)
        f.close()
        os.rename(dd + "_tmp", dd)
        self.saved = True

        #logDir = conf.supybot.directories.log.dirize(self.name())

    def LoadSettings(self):
        dd = self.DataDir() + "/globalvars.pickle"
        f = open(dd, 'r')
        self.sendChannel = load(f)
        self.sendMsg = load(f)
        self.titleFormat = load(f)
        self.nextSendung = load(f)
        self.miscStuff = load(f)
        self.msgSeparator = load(f)
        self.pl = load(f)
        self.sa.load(f)
        f.close()
        self.saved = True

    def Date_FFIM(self, secs):
        dat = localtime(secs)
        while (dat[6] != 4) or (dat[2] > 7):
            secs = secs + (3600 * 24) # add one day
            dat = localtime(secs)

        dat = list(dat)
        dat[3] = 0
        dat[4] = 0
        dat[5] = 0
        dat = tuple(dat)
        
        return dat

    def Date_NextAnnouncement(self, dat):
        doa = mktime(dat)
        doa = doa - (3600 * 24 * 3) # move three days back
        return doa

    def Date_NextFeedback(self, dat):
        doa = mktime(dat)
        doa = doa + (3600 * 4) # advance four hours
        return doa

    def SetTopic(self, topicToSet):
        if self.irc.state.channels[self.sendChannel].topic != topicToSet:
            tmsg = topic(self.sendChannel, topicToSet)
            self.irc.queueMsg(tmsg)
            self.topicAnnounced = True

    def DoAnnouncements(self, curTime):
        nextDay = curTime + (3600 * 24)
        firstFr = self.Date_FFIM(curTime)
        aTime = self.Date_NextAnnouncement(firstFr)
        fTime = self.Date_NextFeedback(firstFr)

        #lt = ctime
        #tmsg = privmsg(self.sendChannel, "atime: %s ftime: %s nextday: %s firstdo: %s curtime: %s" % (lt(aTime), lt(fTime), lt(nextDay), lt(mktime(firstFr)), lt(curTime)))
        #self.irc.queueMsg(tmsg)
        if (aTime <= curTime) and (curTime <= nextDay) and (fTime > curTime):
            if not self.topicAnnounced:
                n_firstFr = self.Date_FFIM(curTime + 3600 * 24 * 3)
                showDate = ctime(mktime(n_firstFr) - 3600)
                mts = self.msgSeparator.join(["Next show: %s (%s)" % (self.nextSendung, showDate)] + self.miscStuff)
                self.SetTopic(mts)
        elif (fTime <= curTime) and (curTime <= nextDay):
            if not self.feedbackAnnounced:
                n_firstFr = self.Date_FFIM(curTime + 3600 * 24 * 3)
                showDate = ctime(mktime(n_firstFr) - 3600)
                mts = self.msgSeparator.join([self.feedbackMsg, "Next show %s" % showDate] + self.miscStuff)
                self.SetTopic(mts)
        else:
            self.feedbackAnnounced = False
            self.topicAnnounced = False

    def flush(self):
        self.SaveSettings()

    def NewLog(self, irc):
        logDir = conf.supybot.directories.log.dirize(self.name())
        if not os.path.exists(logDir):
            os.makedirs(logDir)
        self.saved = False
        try:
            self.logfile = open("%s/CLOG%d.txt" % (logDir, time()), 'w')
        except Exception, e:
            irc.error("Cannot open logfile: %s" % e)
            self.logfile = sys.stderr

    def LogMessage(self, irc, msgtype, message):
        if self.logfile == None:
            self.NewLog(irc)
            if self.logfile == None:
                irc.error("Cannot log message %s" % message)
            else:
                self.LogMessage(irc, "E", "logfile created")
        self.logfile.write("%s %d %s\n" % (msgtype, time(), pformat(message)))
        self.logfile.flush()  # for now, instant flushing only.

    def add(self, irc, msg, args, channel, words):
        """[<channel>] <album>, <title>

        Add a song to the queue. To activate the first
        song in the queue, call 'next'. To show the first
        song in the queue, call 'shownext'"""

        if not self.Checkpriv(irc, msg, channel): return

        words = words.split(",", 1)
        if len(words) != 2:
            irc.error("USAGE EXAMPLE: add Accept, Lady Lou")
            return
        [album, title] = words

        album = album.strip()
        title = title.strip()

        self.LogMessage(irc, "A", {'album': album, 'title': title})

        self.pl.append((album, title))
        self.saved = False
        irc.replySuccess()
    add = wrap(add, ['channel', 'text'])

    def addannounce(self, irc, msg, args, text):
        """<atime> <topic to set>

        Add a new announce. Announce is set as topic no sooner
        than atime. Atime is specified in the format dd.mm.yyyy hh:mm"""

        try: [atime1, atime2, message] = text.split(" ", 2)
        except:
            irc.error("USAGE: addannounce <atime> <topic to set>")
            return
        atime = ' '.join((atime1, atime2))
        try: atime = mktime(strptime(atime, "%d.%m.%Y %H:%M"))
        except Exception, e:
            irc.error("ERROR: atime is in the format dd.mm.yyyy hh:mm")
            print e
        self.sa.additem(atime, message)
        self.saved = False
        irc.reply("Added announce item set for %s" % ctime(atime))
    addannounce = wrap(addannounce, ['text'])

    def answer(self, irc, msg, args, text):
        """<answer>

        Give an answer to a question"""
        try:
            while True: self.lastanswer.pop()
        except: self.lastanswer.append(text)
        irc.reply("Thank you for your assistance, askee")
    answer = wrap(answer, ['text'])

    def showannounces(self, irc, msg, args):
        """

        Show all scheduled announces"""
        if not self.Checkpriv(irc, msg, channel):
            try:
                irc.error("Permission definitely denied")
                raise PermissionException()
            except EOFPermission, e: pass # WTF? EOF shouldn't happen here, but it did in the past, so...
        if len(self.sa.messages) == 0:
            irc.reply("No announces scheduled.")
            return
        for item in self.sa.messages:
            atime, message = item
            irc.reply("%s: %2d %s" % (ctime(atime), self.sa.messages.index(item), message[0:20]))
    showannounces = wrap(showannounces, [])

    def delannounce(self, irc, msg, args, num):
        """<announce ID>

        Delete announce with ID. Use 'showannounces' to get a list of
        pending announces and their IDs. Not that all IDs may change
        after one item has been added / removed."""
        try:
            self.sa.remove(eval(num))
            self.saved = False
            irc.replySuccess()
        except:
            irc.error("Invalid ID")
    delannounce = wrap(delannounce, ['text'])

    def clearannounces(self, irc, msg, args):
        """

        Clear all set announces"""
        try:
            if not self.Checkpriv(irc, msg, channel): return
            self.sa.clear()
            self.saved = False
            irc.replySuccess()
        except: pass
    clearannounces = wrap(clearannounces, [])

    def show(self, irc, msg, args, channel):
        """[<channel>]

            Show complete playlist"""

        if not self.Checkpriv(irc, msg, channel): return

        response = ['There are %d pieces in the playlist.' % len(self.pl)]
        cnt = 0
        for item in self.pl:
            response.append("%d) %s [%s]" % (cnt, item[1], item[0]))
            cnt = cnt + 1
        irc.reply(" | ".join(response))
    show = wrap(show, ['channel'])

    def remove(self, irc, msg, args, channel, entry):
        """[<channel>] ID

            Delete an entry"""
        if not self.Checkpriv(irc, msg, channel): exit(1)
        try:
            album, title = self.pl[int(entry)]
            del self.pl[int(entry)]
            self.LogMessage(irc, "D", {'album': album, 'title': title})
            irc.replySuccess()
            self.saved = False
        except Exception, e: irc.error(pformat(e))
    remove = wrap(remove, ['channel', 'text'])


    def next(self, irc, msg, args, channel):
        """[<channel>]

            Show song that gets activated by calling 'next'"""

        if not self.Checkpriv(irc, msg, channel): return
        if not len(self.pl):
            irc.reply("The playlist is empty")
        else:
            irc.reply("A call to activate would activate %s from %s" % (self.pl[0][1], self.pl[0][0]))
    next = wrap(next, ['channel'])

    def activate(self, irc, msg, args, channel, trackID):
        """[<channel>] [<trackID>]

            Activate song in the playlist. If trackID is not
            specified, the next track in the queue is used."""

        if not self.Checkpriv(irc, msg, channel): return
        if not len(self.pl):
            irc.error("The playlist is empty! Add a song by calling 'add \"album\" \"title\"")
            return

        if not self.sendChannel in irc.state.channels:
            irc.error("I am not joined in %s" % self.sendChannel)
            return

        try:
            album, title = self.pl.pop(trackID)
            self.saved = False
        except:
            irc.error("Invalid ID. Issue !show to see all valid IDs")
            return

        self.playing = {"album": album, "title": title}
        self.DoActivate()
        irc.replySuccess()
    activate = wrap(activate, ['channel', additional('nonNegativeInt', 0)])

    def DoActivate(self):
        irc = self.irc
        self.LogMessage(irc, "M", self.playing)

        #mts = self.sendMsg % (title, album)
        #pmsg = privmsg(self.sendChannel, mts)
        #irc.queueMsg(pmsg)

        mts = irc.state.channels[self.sendChannel].topic
        pos = mts.find(self.sendMsg)
        if pos != -1: mts = mts[0:pos]
        mts = mts + self.sendMsg + (self.titleFormat % self.playing)

        tmsg = topic(self.sendChannel, mts)
        irc.queueMsg(tmsg)

    def finished(self, irc, msg, args, channel):
        """[<channel>]

            Mark a song as finished. Should be called as soon as a song if over."""

        if not self.Checkpriv(irc, msg, channel): return
        if self.playing == None:
            irc.error("No song is playing right now")
            return
        self.DoFinish()
        irc.replySuccess()
    finished = wrap(finished, ['channel'])

    def DoFinish(self):
        self.playing = None
        self.LogMessage(self.irc, "S", self.playing)
        mts = self.irc.state.channels[self.sendChannel].topic
        pos = mts.find(self.sendMsg)
        if pos != -1:
            mts = mts[0:pos]
            tmsg = topic(self.sendChannel, mts)
            self.irc.queueMsg(tmsg)

    def clear(self, irc, msg, args, channel):
        """[<channel>]

            Clear playlist"""
        if not self.Checkpriv(irc, msg, channel): return
        l = len(self.pl)
        while len(self.pl): self.pl.pop()
        parms = [l]
        if l == 1: parms.append("y")
        else: parms.append("ies")
        parms = tuple(parms)
        self.playing = None
        self.LogMessage(irc, "E", "logfile closed")
        self.saved = False
        irc.reply("%d entr%s cleared. New logfile opened." % parms)
        if self.logfile != None:
            try:
                self.logfile.close()
            except: pass
            self.logfile = None
    clear = wrap(clear, ['channel'])

    def nowplaying(self, irc, msg, args, channel):
        """[<channel>]

            Show piece currently playing"""

        if self.playing == None:
            irc.reply("Currently, no song is playing")
        else:
            irc.reply("Now playing %(title)s from %(album)s" % self.playing)
    nowplaying = wrap(nowplaying, ['channel'])

    def help(self, irc, msg, args, text):
        """NO ARGS

            Sorry dude, there ain't no such thing as a help function..."""
        irc.reply("Reed sa soohrss.")
        #irc.reply(irc.state.channels[channel].topic)
    help = wrap(help, [additional('text')])

    def version(self, irc, msg, args, text):
        """NO ARGS

            Version is $Id: plugin.py 63 2008-07-19 07:08:39Z svn $"""
        irc.reply("Version is $Id: plugin.py 63 2008-07-19 07:08:39Z svn $")
        #irc.reply(irc.state.channels[channel].topic)
    version = wrap(version, [additional('text')])



Class = Playlist


# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
