from twisted.words.protocols import irc
from txircd.modbase import Mode
from txircd.utils import irc_lower, epoch, now, CaseInsensitiveDictionary
from fnmatch import fnmatch

class BanMode(Mode):
    def __init__(self):
        self.banMetadata = CaseInsensitiveDictionary()
    
    def checkSet(self, user, target, param):
        if " " in param:
            param = param[:param.index(" ")]
        if "b" in target.mode and len(target.mode["b"]) >= self.ircd.servconfig["channel_ban_list_size"]:
            return [False, param]
        if "!" not in param and "@" not in param:
            param = "{}!*@*".format(param)
        elif "@" not in param:
            param = "{}@*".format(param)
        elif "!" not in param:
            param = "*!{}".format(param)
        if target.name not in self.banMetadata:
            self.banMetadata[target.name] = {}
        self.banMetadata[target.name][param] = [user.nickname, epoch(now())]
        return [True, param]
    
    def checkUnset(self, user, target, param):
        if " " in param:
            param = param[:param.index(" ")]
        if "!" not in param and "@" not in param:
            param = "{}!*@*".format(param)
        elif "@" not in param:
            param = "{}@*".format(param)
        elif "!" not in param:
            param = "*!{}".format(param)
        for banmask in target.mode["b"]:
            if param == banmask:
                if param in self.banMetadata[target.name]:
                    del self.banMetadata[target.name][param]
                return [True, param]
        return [False, param]
    
    def checkPermission(self, user, cmd, data):
        if cmd != "JOIN":
            return data
        channels = data["targetchan"]
        if "ban_evaluating" not in user.cache:
            user.cache["ban_evaluating"] = channels
            return "again"
        keys = data["keys"]
        remove = []
        hostmask = irc_lower(user.prefix())
        for chan in user.cache["ban_evaluating"]:
            if "b" in chan.mode:
                for mask in chan.mode["b"]:
                    if fnmatch(hostmask, irc_lower(mask)):
                        remove.append(chan)
                        user.sendMessage(irc.ERR_BANNEDFROMCHAN, chan.name, ":Cannot join channel (You're banned)")
                        break
        for chan in remove:
            index = channels.index(chan)
            channels.pop(index)
            keys.pop(index)
        data["targetchan"] = channels
        data["keys"] = keys
        del user.cache["ban_evaluating"]
        return data
    
    def showParam(self, user, target):
        if "b" in target.mode:
            for entry in target.mode["b"]:
                metadata = self.banMetadata[target.name][entry] if target.name in self.banMetadata and entry in self.banMetadata[target.name] else [ self.ircd.servconfig["server_name"], epoch(now()) ]
                user.sendMessage(irc.RPL_BANLIST, target.name, entry, metadata[0], str(metadata[1]))
            if target.name in self.banMetadata:
                removeMask = []
                for mask in self.banMetadata[target.name]:
                    if mask not in target.mode["b"]:
                        removeMask.append(mask)
                for mask in removeMask:
                    del self.banMetadata[target.name][mask]
        elif target.name in self.banMetadata:
            del self.banMetadata[target.name] # clear all saved ban data if no bans are set on channel
        user.sendMessage(irc.RPL_ENDOFBANLIST, target.name, ":End of channel ban list")

class Spawner(object):
    def __init__(self, ircd):
        self.ircd = ircd
        self.ban_mode = None
    
    def spawn(self):
        if "channel_ban_list_size" not in self.ircd.servconfig:
            self.ircd.servconfig["channel_ban_list_size"] = 60
        self.ban_mode = BanMode()
        return {
            "modes": {
                "clb": self.ban_mode
            }
        }
    
    def cleanup(self):
        self.ircd.removeMode("clb")
    
    def data_serialize(self):
        return [{}, self.ban_mode.banMetadata._data]
    
    def data_unserialize(self, data):
        for mask, metadata in data.iteritems():
            self.ban_mode.banMetadata[mask] = metadata
@@ -193,92 +193,8 @@
 e):%0A
-    def __init__(self):%0A        self.banMetadata = CaseInsensitiveDictionary()%0A    %0A
     
@@ -696,128 +696,110 @@
  if 
-target.name not in self.banMetadata:%0A            self.banMetadata%5Btarget.name%5D = %7B%7D%0A        self.banMetadata%5Btarget.name
+%22bandata%22 not in target.cache:%0A            target.cache%5B%22bandata%22%5D = %7B%7D%0A        target.cache%5B%22bandata%22
 %5D%5Bpa
@@ -1320,100 +1320,195 @@
  if 
-param in self.banMetadata%5Btarget.name%5D:%0A                    del self.banMetadata%5Btarget.name
+%22bandata%22 in target.cache and param in target.cache%5B%22bandata%22%5D: # Just in case something happened, although bandata shouldn't just disappear%0A                    del target.cache%5B%22bandata%22
 %5D%5Bpa
@@ -2719,121 +2719,103 @@
 a = 
-self.banMetadata%5Btarget.name%5D%5Bentry%5D if target.name in self.banMetadata and entry in self.banMetadata%5Btarget.name
+target.cache%5B%22bandata%22%5D%5Bentry%5D if %22bandata%22 in target.cache and entry in target.cache%5B%22bandata%22
 %5D el
@@ -2991,39 +2991,33 @@
  if 
-target.name in self.banMetadata
+%22bandata%22 in target.cache
 :%0A  
@@ -3078,36 +3078,30 @@
  in 
-self.banMetadata%5Btarget.name
+target.cache%5B%22bandata%22
 %5D:%0A 
@@ -3268,134 +3268,116 @@
 del 
-self.banMetadata%5Btarget.name%5D%5Bmask%5D%0A        elif target.name in self.banMetadata:%0A            del self.banMetadata%5Btarget.name
+target.cache%5B%22bandata%22%5D%5Bmask%5D%0A        elif %22bandata%22 in target.cache:%0A            del target.cache%5B%22bandata%22
 %5D # 
@@ -3982,238 +3982,4 @@
 lb%22)
-%0A    %0A    def data_serialize(self):%0A        return %5B%7B%7D, self.ban_mode.banMetadata._data%5D%0A    %0A    def data_unserialize(self, data):%0A        for mask, metadata in data.iteritems():%0A            self.ban_mode.banMetadata%5Bmask%5D = metadata

Make bans actually smart about how ban metadata is managed