################################################################################
#
# bulletin.py
#
# This module implements a bulletin board system, allowing people to post and
# read messages to various bulletins. This is *not* a substitute for forums. It
# is not nearly as functionally complete as any freely available web-based
# forum. This is meant for posting notes and notices, not for carrying out
# conversations.
#
# contains one command, "bulletin". Syntax is:
#   bulletin
#   bulletin read <board> [article number]
#   bulletin post <board> <title>
#   bulletin delete <board> [article number]
#   bulletin create <board> [groups]
#
# Post makes use of a character's notepad, from the editor module.
#
################################################################################
from mudsys import add_cmd
import mud, storage, char, auxiliary, time, string, hooks



# our table of bulletins. Maps name to bulletin
__bulletins__ = { }

# the file we save our bulletin boards to
__bulletin_file__ = "../lib/misc/bulletins"



class Post:
    '''Implements one bulletin board post'''
    def __init__(self, name, time, title, mssg, set = None):
        '''Prepares the post for use'''
        if not set == None:
            self.__poster__ = set.readString("poster")
            self.__time__   = set.readString("time")
            self.__title__  = set.readString("title")
            self.__mssg__   = set.readString("mssg")
        else:
            self.__poster__ = name
            self.__time__   = time
            self.__title__  = title
            self.__mssg__   = mssg

    def store(self):
        '''returns a storage set representation of the post'''
        set = storage.StorageSet()
        set.storeString("poster", self.__poster__)
        set.storeString("time",   self.__time__)
        set.storeString("title",  self.__title__)
        set.storeString("mssg",   self.__mssg__)
        return set

    # various getters and setters for the Post class
    def setPoster(self, name): self.__poster__ = poster
    def setTime(self, time):   self.__time__   = time
    def setTitle(self, title): self.__title__  = title
    def setMssg(self, mssg):   self.__mssg__   = mssg
    def getPoster(self):       return self.__poster__
    def getTime(self):         return self.__time__
    def getTitle(self):        return self.__title__
    def getMssg(self):         return self.__mssg__



class Bulletin:
    '''Implements a bulletin board'''
    def __init__(self, set = None):
        '''Prepares the bulletin board for use'''
        self.__posts__  = []
        self.__groups__ = ""
        if not set == None:
            self.__groups__ = set.readString("groups")
            for post in set.readList("posts").sets():
                self.__posts__.append(Post(None, None, None, None, post))
            
    def store(self):
        '''returns a storage set representation of the board'''
        set   = storage.StorageSet()
        posts = storage.StorageList()
        set.storeString("groups", self.__groups__)
        set.storeList("posts", posts)
        for post in self.__posts__:
            posts.add(post.store())
        return set

    def post(self, ch, title, mssg):
        '''posts a message onto the bulletin board. Each message is a tuple
           comprised of the poster\'s name, the time of posting, the message
           title, and the message body.'''
        self.__posts__.insert(0, Post(ch.name, time.ctime(), title, mssg))

    def setGroups(self, groups):
        '''sets the groups that have access to this board.'''
        self.__groups__ = groups

    def getGroups(self):
        '''returns the groups that have access to this board.'''
        return self.__groups__

    def getPosts(self):
        '''returns the posts made on the bulletin.'''
        return self.__posts__



################################################################################
# interaction functions
################################################################################
def save_bulletins():
    '''saves all of the bulletin boards to a file'''
    set  = storage.StorageSet()
    list = storage.StorageList()
    set.storeList("list", list)
    for key,val in __bulletins__.iteritems():
        one_set = storage.StorageSet()
        one_set.storeString("key", key)
        one_set.storeSet("val", val.store())
        list.add(one_set)
    set.write(__bulletin_file__)
    set.close()

def load_bulletins():
    set = storage.StorageSet(__bulletin_file__)
    for bulletin in set.readList("list").sets():
        key = bulletin.readString("key")
        __bulletins__[key] = Bulletin(bulletin.readSet("val"))
    set.close()
    return

def do_list_bulletins(ch):
    '''lists all of the bulletins to a character'''
    buf = " %-10s %-28s %-12s %s\n" % ("Bulletins", "Time", "Poster", "Title")
    buf = buf + "--------------------------------------------------------------------------------\n"
    for key in __bulletins__.keys():
        group = __bulletins__[key].getGroups()
        if group == "" or ch.isInGroup(group):

            if len(__bulletins__[key].getPosts()) == 0:
                buf = buf + " %-10s no posts\n" % key
            else:
                post_one = __bulletins__[key].getPosts()[0]
                buf = (buf + " %-10s %-28s %-12s %s\n" %
                       (key, post_one.getTime(), post_one.getPoster(),
                        post_one.getTitle()))
    ch.page(buf)
    return

def do_list_one_bulletin(ch, key):
    '''lists the topics on one bulletin board to a character'''
    if len(__bulletins__[key].getPosts()) == 0:
        ch.send("The " + key + " bulletin is currently empty.")
    else:
        i = 1
        buf = " %-3s %-28s %-12s %s\n" % ("Num", "Time", "Poster", "Title")
        buf = buf + "--------------------------------------------------------------------------------\n"
        for post in __bulletins__[key].getPosts():
            buf = (buf + " %-3d %-28s %-12s %s\n" %
                   (i, post.getTime(), post.getPoster(), post.getTitle()))
            i = i + 1
        ch.page(buf)

def do_post(ch, key, arg):
    '''posts a message to a bulletin board'''
    if not __bulletins__.has_key(key):
        ch.send("Not bulletin named " + key + " exists.")
    elif (not __bulletins__[key].getGroups() == "" and
          not ch.isInGroup(__bulletins__[key].getGroups())):
        ch.send("You are not authorized to post on " + key + ".")
    elif not type(arg) == str:
        ch.send("You must provide a title for your post.")
    elif ch.notepad == "":
        ch.send("Your notepad is currently empty. Try writing something.")
    else:
        board = __bulletins__[key]
        board.post(ch, arg, ch.notepad)
        save_bulletins()
        ch.send("Message posted.")

def do_read(ch, key, arg):
    '''read a specific messge on a board, or list the board messages'''
    if not __bulletins__.has_key(key):
        ch.send("No bulletin named " + key + " exists.")
    elif (not __bulletins__[key].getGroups() == "" and
          not ch.isInGroup(__bulletins__[key].getGroups())):
        ch.send("You are not authorized to read from " + key + ".")
    elif arg == None:
        do_list_one_bulletin(ch, key)
    else:
        try:
            num = string.atoi(arg) - 1
            if num < 0:
                raise IndexError()
            post = __bulletins__[key].getPosts()[num]
            lheader = "By " + post.getPoster() + ": " + post.getTitle()
            header  = "%-50s %29s" % (lheader, post.getTime())
            ch.page(header + "\n" +
                    "--------------------------------------------------------------------------------\n" +
                    post.getMssg())
        except ValueError:
            ch.send("You must provide a post number to read.")
        except IndexError:
            ch.send("That post number does not exist.")

def do_delete(ch, key, arg):
    '''deleted a post from a board, or an entire board'''
    if not __bulletins__.has_key(key):
        ch.send("No bulletin named " + key + " exists.")
    elif (not __bulletins__[key].getGroups() == "" and
          not ch.isInGroup(__bulletins__[key].getGroups())):
        ch.send("You are not authorized to delete from " + key + ".")
    elif ch.isInGroup("admin") and arg == None:
        ch.send("You must confirm the deletion of an entire bulletin.")
    elif ch.isInGroup("admin") and arg == "confirm":
        ch.send("You delete the bulletin, " + key + ".")
        __bulletins__.pop(key)
        save_bulletins()
    else:
        try:
            num = string.atoi(arg) - 1
            if num < 0:
                raise IndexError()
            post = __bulletins__[key].getPosts()[num]
            if post.getPoster() == ch.name or ch.isInGroup("admin"):
                ch.send("Post deleted.")
                __bulletins__[key].getPosts().remove(post)
                save_bulletins()
            else:
                ch.send("You cannot delete \"" + post.getTitle() + "\"")
        except ValueError:
            ch.send("You must provide a post number to delete.")
        except IndexError:
            ch.send("That post number does not exist.")
        


################################################################################
# player commands
################################################################################
def cmd_bulletin(ch, cmd, arg):
    '''The entrypoint into the board system. Allows people to view or post
       messages to bulletin boards. Admin can create/delete boards or messages.

       Syntax:
         bulletin
         bulletin read <board> [article number]
         bulletin post <board> <title>
         bulletin delete <board> [article number]
         bulletin create <board> [groups]
       '''
    # if we have no argument, list the board info
    if arg == '':
        do_list_bulletins(ch)
        return

    # try parsing out our arguments
    try:
        subcmd, board, arg = mud.parse_args(ch, True, cmd, arg,
                                            "word word | string")
    except: return
    
    # figure out what subcommand we were trying to do
    subcmd = subcmd.lower()
    board  = board.lower()
    if subcmd == "read":
        do_read(ch, board, arg)
    elif subcmd == "post":
        do_post(ch, board, arg)
    elif subcmd == "delete":
        do_delete(ch, board, arg)
    elif subcmd == "create" and ch.isInGroup("admin"):
        # create a new bulletin board, with group restrictions if neccessary
        if __bulletins__.has_key(board):
            ch.send("That board already exists.")
        else:
            __bulletins__[board] = Bulletin()
            ch.send("New bulletin, " + board + ", created.")
            if type(arg) == str:
                __bulletins__[board].setGroups(arg)
                ch.send("Bulletin groups set to: " + arg)
            save_bulletins()
    else:
        # let them know what the valid subcommands are
        buf = "Valid board subcommands are read, post, delete"
        if ch.isInGroup("admin"): buf = buf + ", create"
        buf = buf + "."
        ch.send(ch, buf)



################################################################################
# hooks
################################################################################
def bulletin_display_hook(info):
    '''displays the bulletin board info to a character when he enters game'''
    ch, = hooks.parse_info(info)
    do_list_bulletins(ch)



################################################################################
# initialization and unloading
################################################################################
load_bulletins()
hooks.add("char_to_game", bulletin_display_hook)

def __unload__():
    '''things that need to be detached when the module is un/reloaded'''
    hooks.remove("char_to_game", bulletin_display_hook)



################################################################################
# add our commands
################################################################################
add_cmd("bulletin", None, cmd_bulletin, "unconscious", "flying", "player",
        False, False)