from roundup.date import Interval, Date

def join(lol):
    '''Flattens a list of lists into a list'''
    if lol == []:
        return []
    return reduce(lambda x,y: x+y, lol)

def msgfile_reactor(db, cl, issue_id, oldvals):
    '''Ensure that each file is always connected to a message
    '''


    #
    # Figure out which files and messages have been added
    #
    messages = cl.get(issue_id, "messages")
    files = cl.get(issue_id, "files")
    if oldvals and oldvals.has_key("files"):
        added_files = [id for id in files if not id in oldvals["files"]]
    elif not oldvals:
        added_files = files
    else:
        added_files = []

    if oldvals and oldvals.has_key("messages"):
        added_msgs =   [id for id in messages if not id in oldvals["messages"]]
        removed_msgs = [id for id in oldvals["messages"] if not id in messages]
    elif not oldvals:
        added_msgs = messages
        removed_msgs = []
    else:
        added_msgs = []
        removed_msgs = []


    #
    # Make sure that each file is attached to a message
    #

    # Workaround for the web frontend (which allows only
    # one file per message):
    # If the user attaches only one file and no message and
    # the last message is from the same user and less than
    # 5 minutes old, we attach the new file to that message.
    if added_msgs == [] and messages != None and \
            len(messages) > 0 and len(added_files) == 1 and \
            (Date(".") - db.msg.get(messages[-1], "date")) < Interval("0:05"):
        db.msg.set(messages[-1], files=db.msg.get(messages[-1], "files")+added_files)

    # Otherwise we create a new message and attach all files
    # without message to that new message
    else:
        referenced = join([db.msg.get(id, "files") for id in added_msgs])
        to_add = [id for id in added_files if not id in referenced]
        if to_add:
            msg = db.msg.create(
                author = db.file.get(to_add[0], "creator"),
                date = db.file.get(to_add[0], "creation"),
                content = "[attached files]",
                files = to_add)
            messages.append(msg)
            cl.set(issue_id, messages=messages)


    #
    # If a message has been detached, detach its files as well
    #
    for msg_id in removed_msgs:
        for file_id in db.msg.get(msg_id, "files"):
            files.remove(file_id)


            # The following code physically removes the file,
            # if no other messages refer to it
            #if len(db.msg.find(files=file_id)) == 1:
            #    db.file.destroy(file_id)

        # Optionally we also physically remove the message,
        # if it is not attached to any other issue
        if not cl.find(messages=msg_id):
            pass
            #db.msg.destroy(msg_id)

    if files != cl.get(issue_id, "files"):
        cl.set(issue_id, files=files)



def msgfile_auditor(db, cl, issue_id, newdata):
    '''Do not allow direct removal of a file without its message
    '''

    if newdata.has_key("files") and issue_id:
        if newdata.has_key("messages"):
            messages = newdata["messages"]
        else:
            messages = cl.get(issue_id, "messages")

        files = cl.get(issue_id, "files")
        if newdata["files"]:
            removed_files = [id for id in files if not id in newdata["files"]]
        else:
            removed_files = files
        referenced = join([db.msg.get(id, "files") for id in messages])

        for file_id in removed_files:
            if file_id in referenced:
                raise ValueError, "Cannot remove file alone - remove message instead."



def init(db):
    db.issue.react('set', msgfile_reactor)
    db.issue.react('create', msgfile_reactor)
    db.issue.audit('set', msgfile_auditor)

# vim: set filetype=python ts=4 sw=4 et si
