class Command:
  def __init__(self, f_name, f_obj, help, module = "Builtin"):
    self.log = Log()
    self.log.newline()
    self.log.info("Creating command {}".format(f_name))
    self.log.line("-")
    self.name = f_name
    self.func = f_obj
    self.help = self.func.__doc__ or ""
    self.module = module
    self.admin_required = "admin" in self.func.__code__.co_varnames
    self.nargs = self.func.__code__.co_argcount
    self.defaults = self.func.__defaults__
    self.argdefault = {}
    if (self.defaults):
      self.noptargs = len(self.defaults) 
    else:
      self.noptargs = 0
    self.args = self.func.__code__.co_varnames[:self.nargs - self.noptargs]
    self.optargs = self.func.__code__.co_varnames[self.nargs-self.noptargs:
                                                  self.noptargs+1]
    self.hints = typing.get_type_hints(self.func) 
    if "self" in self.args:
      self.nargs -= 1
    self.args = tuple([x for x in self.args if x != "self"])
    for i in range(self.noptargs):
      self.argdefault[self.optargs[i]] = self.defaults[i]

    if self.nargs > 0:
      self.log.info("ARGS: " + ", ".join(self.args))
      self.log.info("OPTARGS: " + ", ".join(self.optargs))
      self.log.info("DEFAULTS: " + ", ".join(
                            ["{}={}".format(
                      i, self.argdefault[i]) for i in self.argdefault]))      
    else:
      self.log.info("No arguments")
    self.delim = ","
    self.NOSPLIT = "NOSPLIT" in self.func.__code__.co_varnames

    self.success = True
    self.log.line("-")
    self.log.info(self)

  def getHelp(self):
    h =  self.name + ":\n" + self.help 
    if self.nargs > 0:
      h += "\nARGS: " + ", ".join(self.args) + "\n" 
    if self.noptargs > 0:
      h +=  "\nOPTARGS: " + " , ".join(self.optargs)
    if "\n" not in h:
      h += " (No Arguments)"
    return h 

  def run(self, args, user_is_admin=False):
    if self.admin_required and not user_is_admin:
      self.log.warning("{} requires administrative permissions".format(self.name))
    self.log.newline()
    self.log.newline()
    self.log.line("+")
    if not self.NOSPLIT:
      args = self._formatArgs(args.split(self.delim))
    else:
      args = self._formatArgs([args])
    self.log.info("Running {} with args {}".format(self.name, args))
    output = self.func(**args)
    self.log.line("+")
    self.log.newline()
    self.log.newline()
    return output
  
  def _formatArgs(self, args):
    args = [x.strip() for x in args if x != '']
    processedArgs = {}
    allargs = self.args + self.optargs
    for i in range(self.nargs):
      arg = ""
      argn = allargs[i]
      if i >= len(args):
        arg = self.argdefault[argn]
      else:
        arg = args[i]
      if allargs[i] in self.hints:
        try:
          arg = self.hints[argn](arg)
        except ValueError as ex:
          raise ArgumentFormatError(ex)
      processedArgs[argn] = arg
    
    return processedArgs

  def __repr__(self):
    x = "\n\nCOMMAND DEFINITION ::- \n"
    return x+"\nCommand (\n Name: {}.{},\n Args: {},\n OptArgs: {}\n Admin: {}\n)\n".format(
            self.module, self.name, self.args, self.optargs, self.admin_required)
class CommandProcessor(threading.Thread):
  """Thread-based module for processing commands. 
	Takes in commands from a queue, processes them asyncronously,
	outputs to another queue.
	ARGS: configfile -- The file to load in all of our configuration from
  """

  def __init__(self, configfile = "command-proc.conf"):
    super(CommandProcessor, self).__init__()
    
    #Open a logger for superior feedback(tm)
    self.log = Log()
    
    #Alert the user that we're starting the processor
    self.log.info("Command Processor Created")
    self.log.newline()
    self.log.info("CMDPROC INIT")
    self.log.line()
    self.log.incIndent()

    #Set up some initial variables
    self.triggers = []
    self.services = []
    self.pipes = []
    self.commands = {}
    self.loadedModules = ["Builtin"]
    
    #Read in the config
    self.log.info("Reading config...")
    self.config = YamlConf(configfile)
    self.log.info("Setting config...")
   
    #Load in the config
    #The getValue() or "Value" means that we have a default
    #So if we can't get something from the config, it defaults to something 
    self.bot_name = self.config.getValue("bot", "name") or "Bot"
    self.bot_version = self.config.getValue("bot", "version") or "0.01"
    self.msg_prefix = self.config.getValue("bot", "msg_prefix") or "BOT: "
    self.command_prefix = self.config.getValue("bot", "cmd_prefix") or "!"
    debug = self.config.getValue("bot", "debug_logging") or False
    module_path = self.config.getValue("modules", "path") or "."

    #This means python will be able to find modules
    #When python looks for things, it goes down a so-called PYTHONPATH
    #We give it some new directories to look in
    self.module_path = module_path 
    sys.path.insert(0, module_path)
    initial_modules = self.config.getValue("modules", "load") or []
    inital_triggers = self.config.getValue("triggers") or []
    self.admins = self.config.getValue("admins")
    self.log.info("Read config.")

    #callback means that we run a function once we're done
    #processing something, so like we can go
      # output = process("!hello")
      # callback(output) 
    #By default it's just print, so we print the output
    self.callback = print

    #Set up the input and output queues
    self.log.info("Initilising command queue...")
    self.cmdQ = queue.Queue()
    self.log.info("Initilising output queue...")
    self.outputQ = queue.Queue()

    #Set up some stuff so we can tell the commandprocessor to stop
    #when we want to exit, we turn the Event on
    self.log.info("Initilising requests...")
    self.stopReq = threading.Event()
    self.stopNOW = threading.Event()

    #We have a few "initial modules", things to load on startup
    self.log.info("Setting up modules...")
    for i in initial_modules:
      self.loadModule(i)

    #Same goes for triggers
    self.log.info("Setting up triggers...")
    for i in inital_triggers:
      self.addTrigger(i, inital_triggers[i])

    #If the user wants more information than usual, they can set debug to True
    self.log.info("Setting verbosity...")
    if debug:
      self.log.setLevel(self.log.DEBUG)

    #For reference: an underscore before the function name means that it only has INTERNAL use,
    #i.e nobody outside this class should call _addUtilityCommands()
    self._addUtilityCommands()

    #Tell the user that we've finished setting everything up
    self.log.line()
    self.log.info("FINISHED CMDPROC INIT")   
    self.log.newline()
  
  def join(self, timeout=None):
    """When the owner of CommandProcessor wants us to exit, 
    we alert the user that we've recieved the quit request,
    and shut everythig down
    """

    self.log.info("QUITTING")
    self.log.line("Q")
    #Tell the processing function to stop
    self.stopReq.set()
  
    #Join the thread to the main process,
    #wait for `timeout` seconds before forcing it to.
    super(Thread, self).join(timeout)
  
  def addCommand(self, function_name, function_object, help=None, module="Builtin"):
    """Add a command to the processor
       ARGS:
        function_name: The name you wish to call the function by, i.e "func" 
        function_object: The actual object to run when you call the command
    """

    self.log.info("Adding command {}".format(function_name))

    #Check if we've already got a command by name `function_name`    
    if function_name in self.commands:
      self.log.info("Command {} already registered. Overwriting")
    
    #Make a nice little command object, for keeping track of it
    com = Command(function_name, function_object, help=help, module=module)

    #Make sure it actually worked yo
    if com.success:
      self.commands[function_name] = com
    else:
      self.log.info("Failed to add command")

  def removeCommand(self, function_name):
    """WHAT THE FEK DO YOU THINK IT DOES? MAKE CAKE? NO. IT REMOVES A COMMAND!"""

    #Check that we've actually got a command by that name
    if function_name in self.commands:
      #If we do, delete it!
      del self.commands[function_name]
      self.log.info("Succesfully removed {}".format(function_name))
    else:
      #Otherwise, we can't. Because you can't delete something that doesn't exist, dummy
      self.log.info("Could not remove non-existent function {}".format(function_name))
  
  def addTrigger(self, trigger_text, replacement_text):
    """Add a text-based trigger - will send replacement_text when
       trigger_text is in a given message"""

    #Check if we've got a trigger about that already, because we might
    for trigger in self.triggers:
      if trigger.trigger == trigger_text:
        #If we do, modifify it to the new trigger
        trigger.send_text = replacement_text
        return ("Modified trigger to {}".format(trigger))
        
    #If we haven't got a trigger about `trigger_text`, 
    #make a new one
    self.triggers.append(Trigger(trigger_text, replacement_text))
    self.log.info("Added {}".format(self.triggers[-1]))
    return ("Added -- {}".format(self.triggers[-1]))

  def writeConfig(self):
    """Save the config, all currently loaded modules will be saved to init_modules"""
    self.log.info("Writing config...")

    #Add all of our triggers to a nice little dictionary,
    #So we can write it to a file
    trigs = {}
    for i in self.triggers:
      trigs[i.trigger] = i.send_text

    settings = {
        "bot" : {
          "name": self.bot_name,
          "version": self.bot_version,
          "msg_prefix": self.msg_prefix,
          "cmd_prefix": self.command_prefix,
          "debug_logging": False
        },
        "modules" : {
          "path": self.module_path,
          "load": self.loadedModules
        },
        "triggers": trigs,
        "ai": {
          "model_directory": "models"
        },
        "admins": self.admins
      } 

    self.log.info(settings)
    
    #Write the config in YAML format 
    with open("command-proc.conf", "w") as f:
      f.write(pyaml.dump(settings))
    self.log.info("Written")

  def removeTrigger(self, txt):
    """Remove the trigger with `trigger_text` == `txt`"""

    for trigger in self.triggers:
      if trigger.trigger == txt:
        self.triggers.remove(trigger)
        return ("Removed {}".format(trigger))

  def setCallback(self, function):
    """Set the function to run on function complete
       ARGS: function (python function)"""
    self.callback = function

  def _process(self, command):
    """Internal process command - parse the command and execute"""
    self.log.info("Processing request {}...".format(command[0]))

    #Remove trailing and preceeding spaces
    
    #isinstance checks if command is a string or list
      #e.g isinstance("hello", str) is True
      #isinstance("hello", list) is False

    if isinstance(command, str):
      command = [command, None]
    if isinstance(command[0], list):
      command = command[0]
    command[0] = command[0].strip()

    try: 
      #Check if it's a command or not
      if command[0][0] == self.command_prefix:
        #It's a command
        self._checkAgainstCommands(command)
      else:
        #We'll check it against the triggers
        self._checkAgainstTriggers(command)
    except Exception:
      pass

  def _checkAgainstCommands(self, command):
    command,channel = command
    command = command[1:]
    command_name,sep,args = command.partition(" ")
    if command_name in self.commands:
      #Now we've verified, go ahead and run it
      try:
        cmd = self.commands[command_name].run(args)
        if type(cmd) == types.GeneratorType:
          for i in cmd:
            self.output([i, channel])
        else:
          self.output([cmd, channel])

      except ArgumentFormatError:
        self.output("Error running {} -- Argument format error".format(command_name))
      except TypeError:
        pass

    else:

      self.log.info("No command of name {} detected".format(command_name))
      self.output("Error running {} -- Command not found".format(command_name))
  def output(self, val):
    self.outputQ.put(val)
    if self.callback:
      self.callback(*val)

  def _checkAgainstTriggers(self, command):
    for i in self.triggers:
      if i.match(command[0]):
        self.output([i.send_text, command[1]])

  def getOutput(self):
    try:
      x = self.outputQ.get(False)
    except queue.Empty:
      return None
    return x

  def run(self):
    """Start the thread off"""
    self.log.info("Command processor thread starting...")
    while (not self.stopReq.isSet()) or self.cmdQ.qsize() != 0:
      if self.stopNOW.isSet():
        break
      try:
        toProcess = self.cmdQ.get(True, 0.5)
        self._process(toProcess)
      except queue.Empty:
        continue
      except Exception as e:
        self.log.error(e)
        traceback.format_exc(e)

    self.log.info("Stopping with qsize: {}".format(self.cmdQ.qsize()))
    self.log.info("Stopreq state: {}".format(self.stopReq.isSet()))
    self.log.info("Thread closed.")
        

  def push(self, commandstring, assoc_info = None):
    """Add a command to the command queue - to be processed commands
       ARGS: commandstring (str) - the command to process
             assoc_info (Any) - Things to be returned with the processed cmd"""
    self.log.info("Pushing command {}".format(commandstring))
    self.cmdQ.put([commandstring, assoc_info])
    for pipe in self.pipes:
      pipe.put([commandstring, assoc_info])
  
  def exit(self, now=False):
    """Quit the thread, cleanly exit"""
    admin = 1
    yield "Closing..."
    self.log.info("Exit request acknowledged, will exit.")
    self.stopReq.set()
    if (now):
      self.stopNOW.set()
    
  ##Module loading/unloading
  def loadModule(self, name):
    self.log.newline() 
    self.log.info("LOADING MODULE {}".format(name.upper())) 
    self.log.line()
    self.log.incIndent()
    try:
      ##Try loading the module as i
      yield("Importing {}...".format(name))
      i = importlib.import_module(name)
      ##In case it's changed and was imported before, reload it 
      self.log.debug("Reloading...")
      i = importlib.reload(i)
      ##Get a list of all the functions defined in the module
      self.log.debug("Getting functions...")
      funcs = dir(i)
      ##Don't import python's internal functions, like __name__ and __init__
      x = re.compile("__[a-z]*__")
      z = ([("i.{}".format(y)) for y in funcs if not (x.match(y) or y[-1]=="_")])
      self.log.debug("Loaded, adding functions...")
      self.log.incIndent()
      self.funcs = "Loaded functions:\n"
      ##Load the functions in
      for j in z:
        if type(eval(j)) == types.FunctionType:
          if "onimport" in j:
            self.log.info("Running import function...")
            eval(j)()
          elif "onexit" in j:
            atexit.register(eval(j))
          else:  
            self.addCommand(j.split(".")[1], eval(j), module=name)
            self.funcs += "!{}, ".format(j.split(".")[1])
      if name not in self.loadedModules:
        self.loadedModules.append(name)
    
      yield(self.funcs)
    except ImportError as ie:
      self.log.error("Could not find module {}".format(name))
      self.log.error(ie)
    except Exception as e:
      self.log.error("Unknown exception: {}".format(e)) 
    
  def unloadModule(self, module_name):
    yield "Unloading module {}".format(module_name)
    funcs = "( "
    for i in self.commands:
      if self.commands[i].module == module_name:
        self.removeCommand(i)
        funcs += i + ", "
    self.loadedModules.remove(module_name)
    self.log.info("Unloaded {} {} )".format(module_name, funcs))

  def lsmod(self):
    """List all currently loaded modules"""
    x = "Loaded modules: \n"
    x += "\n".join(self.loadedModules)
    return( x + "\n" )
  
  def getHelp(self, cmd=None):
    if cmd:
      if cmd in self.commands:
        return(self.commands[cmd].getHelp())
      else:
        return("Command {} does not exist".format(cmd))
    else:
      #Return list of commands
      return "Available Commands: "+", ".join(self.commands)
  def listTriggers(self):
    return "\n".join([str(x) for x in self.triggers])

  def loadService(self, srvname):
    self.services.append(
      Service( 
        name = srvname,
        function = function,
        autostart = True
      )
    )

  def startService(self, srvname):
    for i in self.services:
      if i.name == srvname:
        i.start()     
        return "Started" 
    return "Service not found"

  def killService(self, srvname):
    for i in self.services:
      if i.name == srvname:
        i.stop()
        self.services.remove(i)
        return "Killed"
    return "Service not found"

  
  def lsService(self):
    x = [i.name for i in self.services]
    return "\n".join(x)

  def _addUtilityCommands(self):
    self.addCommand("lsmod", self.lsmod)
    self.addCommand("import", self.loadModule)
    self.addCommand("quit", self.exit)
    self.addCommand("help", self.getHelp)
    self.addCommand("mktrig", self.addTrigger)
    self.addCommand("rmtrig", self.removeTrigger)
    self.addCommand("lstrig", self.listTriggers)
    self.addCommand("writeconf", self.writeConfig)
    self.addCommand("loadSrv", self.loadService)
    self.addCommand("startSrv", self.startService)
    self.addCommand("killSrv", self.killService)
    self.addCommand("lsSrv", self.lsService)
class Command:
    def __init__(self, f_name, f_obj, help, module="Builtin"):
        self.log = Log()
        self.log.newline()
        self.log.info("Creating command {}".format(f_name))
        self.log.line("-")
        self.name = f_name
        self.func = f_obj
        self.help = self.func.__doc__ or ""
        self.module = module
        self.admin_required = "admin" in self.func.__code__.co_varnames
        self.nargs = self.func.__code__.co_argcount
        self.defaults = self.func.__defaults__
        self.argdefault = {}
        if (self.defaults):
            self.noptargs = len(self.defaults)
        else:
            self.noptargs = 0
        self.args = self.func.__code__.co_varnames[:self.nargs - self.noptargs]
        self.optargs = self.func.__code__.co_varnames[self.nargs - self.
                                                      noptargs:self.noptargs +
                                                      1]
        self.hints = typing.get_type_hints(self.func)
        if "self" in self.args:
            self.nargs -= 1
        self.args = tuple([x for x in self.args if x != "self"])
        for i in range(self.noptargs):
            self.argdefault[self.optargs[i]] = self.defaults[i]

        if self.nargs > 0:
            self.log.info("ARGS: " + ", ".join(self.args))
            self.log.info("OPTARGS: " + ", ".join(self.optargs))
            self.log.info("DEFAULTS: " + ", ".join([
                "{}={}".format(i, self.argdefault[i]) for i in self.argdefault
            ]))
        else:
            self.log.info("No arguments")
        self.delim = ","
        self.NOSPLIT = "NOSPLIT" in self.func.__code__.co_varnames

        self.success = True
        self.log.line("-")
        self.log.info(self)

    def getHelp(self):
        h = self.name + ":\n" + self.help
        if self.nargs > 0:
            h += "\nARGS: " + ", ".join(self.args) + "\n"
        if self.noptargs > 0:
            h += "\nOPTARGS: " + " , ".join(self.optargs)
        if "\n" not in h:
            h += " (No Arguments)"
        return h

    def run(self, args, user_is_admin=False):
        if self.admin_required and not user_is_admin:
            self.log.warning("{} requires administrative permissions".format(
                self.name))
        self.log.newline()
        self.log.newline()
        self.log.line("+")
        if not self.NOSPLIT:
            args = self._formatArgs(args.split(self.delim))
        else:
            args = self._formatArgs([args])
        self.log.info("Running {} with args {}".format(self.name, args))
        output = self.func(**args)
        self.log.line("+")
        self.log.newline()
        self.log.newline()
        return output

    def _formatArgs(self, args):
        args = [x.strip() for x in args if x != '']
        processedArgs = {}
        allargs = self.args + self.optargs
        for i in range(self.nargs):
            arg = ""
            argn = allargs[i]
            if i >= len(args):
                arg = self.argdefault[argn]
            else:
                arg = args[i]
            if allargs[i] in self.hints:
                try:
                    arg = self.hints[argn](arg)
                except ValueError as ex:
                    raise ArgumentFormatError(ex)
            processedArgs[argn] = arg

        return processedArgs

    def __repr__(self):
        x = "\n\nCOMMAND DEFINITION ::- \n"
        return x + "\nCommand (\n Name: {}.{},\n Args: {},\n OptArgs: {}\n Admin: {}\n)\n".format(
            self.module, self.name, self.args, self.optargs,
            self.admin_required)
class Chat:
  """Represents a chat class in a skype client"""
  def __init__(self): 

    self.log = Log()    
    __builtins__["chat"] = self
    self.log.newline()
    self.log.info("SKYPE BOT INIT")
    self.log.line("+",40)
    self.log.incIndent()
    self.log.newline()
    self.log.info("SKYPE4PY STARTUP")
    self.log.line()
    self.log.incIndent()
    self.s = skype.Skype()
    ##Connect to the API
    self.s.Attach()
    self.log.decIndent()
    self.log.line()
    self.log.info("SKYPE4PY STARTED")
    self.log.newline()
    ##Set initial variables
    self.lastMessage = None
    
    ##Create a processor for all possible commands
    self.cmdProc = commandprocessor.CommandProcessor()

    ##Try to connect to the chat
    try:
      self.chat = self.s.ActiveChats[0]
    except Exception as e:
      print("Cloud chat not supported! {}".format(e))
      sys.exit(1)
  
    ##Allow for some twiddling
    self.lastMessage = self.chat.Messages[0]
 
    ##Alert users to the bot presence
    self.log.line("+", 40)
    self.log.info("FINISHED SKYPE BOT INIT")
    self.log.newline()

  def send(self, msg):
    """Send a message to the chat"""
    if msg != None and msg != "" and type(msg) != type(True):
      self.chat.SendMessage(">>>"+msg)

  def processMessage(self):
    """Figure out what to do with the last received message"""
    if ">>>" not in self.lastMessage.Body:
      self.log.info("Processing {}".format(self.lastMessage.Body))
      self.cmdProc.push([self.lastMessage.Body, None])
    
  def mainloop(self):
    self.log.info("Starting mainloop...")
    self.cmdProc.start()
    threading.Thread(target=self.getOutput).start()
    self.log.info("Started thread.")
    while 1:
      try:
        newmsg = self.chat.Messages[0]
        if self.lastMessage != newmsg:
          self.lastMessage = newmsg
          self.processMessage()
      except KeyboardInterrupt:
        sys.exit()
   
  def addCommand(self, cmdName, func):
    self.cmdProc.addCommand(cmdName, func)

  def getAllBy(self, handle):
    return [i.Body for i in self.chat.Messages if str(i.FromHandle).lower() == handle]

  def getOutput(self):
    self.log.info("Starting output thread...")
    while True:
      x = (self.cmdProc.getOutput())
      if type(x) == list:
        self.send(x[0])
      else:
        self.send(x)
class CommandProcessor(threading.Thread):
    """Thread-based module for processing commands. 
	Takes in commands from a queue, processes them asyncronously,
	outputs to another queue.
	ARGS: configfile -- The file to load in all of our configuration from
  """
    def __init__(self, configfile="command-proc.conf"):
        super(CommandProcessor, self).__init__()

        #Open a logger for superior feedback(tm)
        self.log = Log()

        #Alert the user that we're starting the processor
        self.log.info("Command Processor Created")
        self.log.newline()
        self.log.info("CMDPROC INIT")
        self.log.line()
        self.log.incIndent()

        #Set up some initial variables
        self.triggers = []
        self.services = []
        self.pipes = []
        self.commands = {}
        self.loadedModules = ["Builtin"]

        #Read in the config
        self.log.info("Reading config...")
        self.config = YamlConf(configfile)
        self.log.info("Setting config...")

        #Load in the config
        #The getValue() or "Value" means that we have a default
        #So if we can't get something from the config, it defaults to something
        self.bot_name = self.config.getValue("bot", "name") or "Bot"
        self.bot_version = self.config.getValue("bot", "version") or "0.01"
        self.msg_prefix = self.config.getValue("bot", "msg_prefix") or "BOT: "
        self.command_prefix = self.config.getValue("bot", "cmd_prefix") or "!"
        debug = self.config.getValue("bot", "debug_logging") or False
        module_path = self.config.getValue("modules", "path") or "."

        #This means python will be able to find modules
        #When python looks for things, it goes down a so-called PYTHONPATH
        #We give it some new directories to look in
        self.module_path = module_path
        sys.path.insert(0, module_path)
        initial_modules = self.config.getValue("modules", "load") or []
        inital_triggers = self.config.getValue("triggers") or []
        self.admins = self.config.getValue("admins")
        self.log.info("Read config.")

        #callback means that we run a function once we're done
        #processing something, so like we can go
        # output = process("!hello")
        # callback(output)
        #By default it's just print, so we print the output
        self.callback = print

        #Set up the input and output queues
        self.log.info("Initilising command queue...")
        self.cmdQ = queue.Queue()
        self.log.info("Initilising output queue...")
        self.outputQ = queue.Queue()

        #Set up some stuff so we can tell the commandprocessor to stop
        #when we want to exit, we turn the Event on
        self.log.info("Initilising requests...")
        self.stopReq = threading.Event()
        self.stopNOW = threading.Event()

        #We have a few "initial modules", things to load on startup
        self.log.info("Setting up modules...")
        for i in initial_modules:
            self.loadModule(i)

        #Same goes for triggers
        self.log.info("Setting up triggers...")
        for i in inital_triggers:
            self.addTrigger(i, inital_triggers[i])

        #If the user wants more information than usual, they can set debug to True
        self.log.info("Setting verbosity...")
        if debug:
            self.log.setLevel(self.log.DEBUG)

        #For reference: an underscore before the function name means that it only has INTERNAL use,
        #i.e nobody outside this class should call _addUtilityCommands()
        self._addUtilityCommands()

        #Tell the user that we've finished setting everything up
        self.log.line()
        self.log.info("FINISHED CMDPROC INIT")
        self.log.newline()

    def join(self, timeout=None):
        """When the owner of CommandProcessor wants us to exit, 
    we alert the user that we've recieved the quit request,
    and shut everythig down
    """

        self.log.info("QUITTING")
        self.log.line("Q")
        #Tell the processing function to stop
        self.stopReq.set()

        #Join the thread to the main process,
        #wait for `timeout` seconds before forcing it to.
        super(Thread, self).join(timeout)

    def addCommand(self,
                   function_name,
                   function_object,
                   help=None,
                   module="Builtin"):
        """Add a command to the processor
       ARGS:
        function_name: The name you wish to call the function by, i.e "func" 
        function_object: The actual object to run when you call the command
    """

        self.log.info("Adding command {}".format(function_name))

        #Check if we've already got a command by name `function_name`
        if function_name in self.commands:
            self.log.info("Command {} already registered. Overwriting")

        #Make a nice little command object, for keeping track of it
        com = Command(function_name, function_object, help=help, module=module)

        #Make sure it actually worked yo
        if com.success:
            self.commands[function_name] = com
        else:
            self.log.info("Failed to add command")

    def removeCommand(self, function_name):
        """WHAT THE FEK DO YOU THINK IT DOES? MAKE CAKE? NO. IT REMOVES A COMMAND!"""

        #Check that we've actually got a command by that name
        if function_name in self.commands:
            #If we do, delete it!
            del self.commands[function_name]
            self.log.info("Succesfully removed {}".format(function_name))
        else:
            #Otherwise, we can't. Because you can't delete something that doesn't exist, dummy
            self.log.info("Could not remove non-existent function {}".format(
                function_name))

    def addTrigger(self, trigger_text, replacement_text):
        """Add a text-based trigger - will send replacement_text when
       trigger_text is in a given message"""

        #Check if we've got a trigger about that already, because we might
        for trigger in self.triggers:
            if trigger.trigger == trigger_text:
                #If we do, modifify it to the new trigger
                trigger.send_text = replacement_text
                return ("Modified trigger to {}".format(trigger))

        #If we haven't got a trigger about `trigger_text`,
        #make a new one
        self.triggers.append(Trigger(trigger_text, replacement_text))
        self.log.info("Added {}".format(self.triggers[-1]))
        return ("Added -- {}".format(self.triggers[-1]))

    def writeConfig(self):
        """Save the config, all currently loaded modules will be saved to init_modules"""
        self.log.info("Writing config...")

        #Add all of our triggers to a nice little dictionary,
        #So we can write it to a file
        trigs = {}
        for i in self.triggers:
            trigs[i.trigger] = i.send_text

        settings = {
            "bot": {
                "name": self.bot_name,
                "version": self.bot_version,
                "msg_prefix": self.msg_prefix,
                "cmd_prefix": self.command_prefix,
                "debug_logging": False
            },
            "modules": {
                "path": self.module_path,
                "load": self.loadedModules
            },
            "triggers": trigs,
            "ai": {
                "model_directory": "models"
            },
            "admins": self.admins
        }

        self.log.info(settings)

        #Write the config in YAML format
        with open("command-proc.conf", "w") as f:
            f.write(pyaml.dump(settings))
        self.log.info("Written")

    def removeTrigger(self, txt):
        """Remove the trigger with `trigger_text` == `txt`"""

        for trigger in self.triggers:
            if trigger.trigger == txt:
                self.triggers.remove(trigger)
                return ("Removed {}".format(trigger))

    def setCallback(self, function):
        """Set the function to run on function complete
       ARGS: function (python function)"""
        self.callback = function

    def _process(self, command):
        """Internal process command - parse the command and execute"""
        self.log.info("Processing request {}...".format(command[0]))

        #Remove trailing and preceeding spaces

        #isinstance checks if command is a string or list
        #e.g isinstance("hello", str) is True
        #isinstance("hello", list) is False

        if isinstance(command, str):
            command = [command, None]
        if isinstance(command[0], list):
            command = command[0]
        command[0] = command[0].strip()

        try:
            #Check if it's a command or not
            if command[0][0] == self.command_prefix:
                #It's a command
                self._checkAgainstCommands(command)
            else:
                #We'll check it against the triggers
                self._checkAgainstTriggers(command)
        except Exception:
            pass

    def _checkAgainstCommands(self, command):
        command, channel = command
        command = command[1:]
        command_name, sep, args = command.partition(" ")
        if command_name in self.commands:
            #Now we've verified, go ahead and run it
            try:
                cmd = self.commands[command_name].run(args)
                if type(cmd) == types.GeneratorType:
                    for i in cmd:
                        self.output([i, channel])
                else:
                    self.output([cmd, channel])

            except ArgumentFormatError:
                self.output("Error running {} -- Argument format error".format(
                    command_name))
            except TypeError:
                pass

        else:

            self.log.info(
                "No command of name {} detected".format(command_name))
            self.output(
                "Error running {} -- Command not found".format(command_name))

    def output(self, val):
        self.outputQ.put(val)
        if self.callback:
            self.callback(*val)

    def _checkAgainstTriggers(self, command):
        for i in self.triggers:
            if i.match(command[0]):
                self.output([i.send_text, command[1]])

    def getOutput(self):
        try:
            x = self.outputQ.get(False)
        except queue.Empty:
            return None
        return x

    def run(self):
        """Start the thread off"""
        self.log.info("Command processor thread starting...")
        while (not self.stopReq.isSet()) or self.cmdQ.qsize() != 0:
            if self.stopNOW.isSet():
                break
            try:
                toProcess = self.cmdQ.get(True, 0.5)
                self._process(toProcess)
            except queue.Empty:
                continue
            except Exception as e:
                self.log.error(e)
                traceback.format_exc(e)

        self.log.info("Stopping with qsize: {}".format(self.cmdQ.qsize()))
        self.log.info("Stopreq state: {}".format(self.stopReq.isSet()))
        self.log.info("Thread closed.")

    def push(self, commandstring, assoc_info=None):
        """Add a command to the command queue - to be processed commands
       ARGS: commandstring (str) - the command to process
             assoc_info (Any) - Things to be returned with the processed cmd"""
        self.log.info("Pushing command {}".format(commandstring))
        self.cmdQ.put([commandstring, assoc_info])
        for pipe in self.pipes:
            pipe.put([commandstring, assoc_info])

    def exit(self, now=False):
        """Quit the thread, cleanly exit"""
        admin = 1
        yield "Closing..."
        self.log.info("Exit request acknowledged, will exit.")
        self.stopReq.set()
        if (now):
            self.stopNOW.set()

    ##Module loading/unloading
    def loadModule(self, name):
        self.log.newline()
        self.log.info("LOADING MODULE {}".format(name.upper()))
        self.log.line()
        self.log.incIndent()
        try:
            ##Try loading the module as i
            yield ("Importing {}...".format(name))
            i = importlib.import_module(name)
            ##In case it's changed and was imported before, reload it
            self.log.debug("Reloading...")
            i = importlib.reload(i)
            ##Get a list of all the functions defined in the module
            self.log.debug("Getting functions...")
            funcs = dir(i)
            ##Don't import python's internal functions, like __name__ and __init__
            x = re.compile("__[a-z]*__")
            z = ([("i.{}".format(y)) for y in funcs
                  if not (x.match(y) or y[-1] == "_")])
            self.log.debug("Loaded, adding functions...")
            self.log.incIndent()
            self.funcs = "Loaded functions:\n"
            ##Load the functions in
            for j in z:
                if type(eval(j)) == types.FunctionType:
                    if "onimport" in j:
                        self.log.info("Running import function...")
                        eval(j)()
                    elif "onexit" in j:
                        atexit.register(eval(j))
                    else:
                        self.addCommand(j.split(".")[1], eval(j), module=name)
                        self.funcs += "!{}, ".format(j.split(".")[1])
            if name not in self.loadedModules:
                self.loadedModules.append(name)

            yield (self.funcs)
        except ImportError as ie:
            self.log.error("Could not find module {}".format(name))
            self.log.error(ie)
        except Exception as e:
            self.log.error("Unknown exception: {}".format(e))

    def unloadModule(self, module_name):
        yield "Unloading module {}".format(module_name)
        funcs = "( "
        for i in self.commands:
            if self.commands[i].module == module_name:
                self.removeCommand(i)
                funcs += i + ", "
        self.loadedModules.remove(module_name)
        self.log.info("Unloaded {} {} )".format(module_name, funcs))

    def lsmod(self):
        """List all currently loaded modules"""
        x = "Loaded modules: \n"
        x += "\n".join(self.loadedModules)
        return (x + "\n")

    def getHelp(self, cmd=None):
        if cmd:
            if cmd in self.commands:
                return (self.commands[cmd].getHelp())
            else:
                return ("Command {} does not exist".format(cmd))
        else:
            #Return list of commands
            return "Available Commands: " + ", ".join(self.commands)

    def listTriggers(self):
        return "\n".join([str(x) for x in self.triggers])

    def loadService(self, srvname):
        self.services.append(
            Service(name=srvname, function=function, autostart=True))

    def startService(self, srvname):
        for i in self.services:
            if i.name == srvname:
                i.start()
                return "Started"
        return "Service not found"

    def killService(self, srvname):
        for i in self.services:
            if i.name == srvname:
                i.stop()
                self.services.remove(i)
                return "Killed"
        return "Service not found"

    def lsService(self):
        x = [i.name for i in self.services]
        return "\n".join(x)

    def _addUtilityCommands(self):
        self.addCommand("lsmod", self.lsmod)
        self.addCommand("import", self.loadModule)
        self.addCommand("quit", self.exit)
        self.addCommand("help", self.getHelp)
        self.addCommand("mktrig", self.addTrigger)
        self.addCommand("rmtrig", self.removeTrigger)
        self.addCommand("lstrig", self.listTriggers)
        self.addCommand("writeconf", self.writeConfig)
        self.addCommand("loadSrv", self.loadService)
        self.addCommand("startSrv", self.startService)
        self.addCommand("killSrv", self.killService)
        self.addCommand("lsSrv", self.lsService)