import json
import re
from collections import OrderedDict
from typing import List

from pyxdameraulevenshtein import normalized_damerau_levenshtein_distance as ndld
from twisted.plugin import IPlugin
from zope.interface import implementer

from desertbot.message import IRCMessage
from desertbot.moduleinterface import IModule
from desertbot.modules.commandinterface import BotCommand
from desertbot.response import IRCResponse


@implementer(IPlugin, IModule)
class StringUtils(BotCommand):
    def triggers(self):
        return ["tojson", "fromjson"]

    def actions(self):
        return super(StringUtils, self).actions() + [('closest-matches', 1, self.closestMatches)]

    def closestMatches(self, search: str, wordList: List[str],
                       numMatches: int, threshold: float) -> List[str]:
        similarities = sorted([(ndld(search, word), word) for word in wordList])
        closeMatches = [word for (diff, word) in similarities if diff <= threshold]
        topN = closeMatches[:numMatches]
        return topN

    def _tojson(self, message: IRCMessage):
        """converts input string to json-escaped string"""
        return IRCResponse(json.dumps(message.parameters), message.replyTo)

    def _fromjson(self, message: IRCMessage):
        """un-escapes json strings"""
        return IRCResponse(str(json.loads(message.parameters)), message.replyTo)

    commands = OrderedDict([
        ('tojson', _tojson),
        ('fromjson', _fromjson),
    ])

    def execute(self, message: IRCMessage):
        command = message.command.lower()
        if command in self.commands:
            return self.commands[command](self, message)
        else:
            return IRCResponse(f'"{message.command}" is not a recognized StringUtils command', message.replyTo)

    def help(self, query):
        command = query.lower()
        if command in self.commands:
            doc = re.sub(r"\s+", " ", self.commands[command].__doc__)
            return f"{self.bot.commandChar}{command} {doc}"


stringUtils = StringUtils()

@@ -545,16 +545,43 @@
 romjson%22
+, %22prevmsg%22, %22prev_or_args%22
 %5D%0A%0A    d
@@ -694,16 +694,396 @@
 Matches)
+,%0A                                                     ('message-channel', 1, self._storeMessage),%0A                                                     ('message-user', 1, self._storeMessage),%0A                                                     ('action-channel', 1, self._storeMessage),%0A                                                     ('action-user', 1, self._storeMessage)
 %5D%0A%0A    d
@@ -1781,24 +1781,1250 @@
 e.replyTo)%0A%0A
+    def _storeMessage(self, message: IRCMessage):%0A        %22%22%22stores the current message for _prevmsg to return later%22%22%22%0A        if message.command and message.command.lower() in self.bot.moduleHandler.mappedTriggers:%0A            # ignore bot commands%0A            return%0A%0A        if 'tracking' in message.metadata:%0A            # ignore internal messages from alias processing%0A            if any(m in message.metadata%5B'tracking'%5D for m in %5B'Sub', 'Chain', 'Alias'%5D):%0A                return%0A%0A        self.messages%5Bmessage.replyTo%5D = message%0A%0A    def _prevmsg(self, message: IRCMessage):%0A        %22%22%22returns the previous message from the current channel%22%22%22%0A        if message.replyTo not in self.messages:%0A            return IRCResponse(%22No previous message stored for this channel yet%22, message.replyTo)%0A        msg = self.messages%5Bmessage.replyTo%5D%0A        return IRCResponse(msg.messageString, message.replyTo)%0A%0A    def _prev_or_args(self, message: IRCMessage):%0A        %22%22%22returns the previous message from the current channel,%0A        or the command arguments if given%22%22%22%0A        if message.parameters:%0A            return IRCResponse(message.parameters, message.replyTo)%0A        else:%0A            return self._prevmsg(message)%0A%0A
     commands
@@ -3098,24 +3098,96 @@
 _fromjson),%0A
+        ('prevmsg', _prevmsg),%0A        ('prev_or_args', _prev_or_args),%0A
     %5D)%0A%0A    

add prevmsg and prev_or_args to replace aliases for the same