import json
import re
import ply.lex as lex
from flask import session
from flask_login import current_user
from requests import get
from integration.markdown.emojis.shortCodeDict import slackToZulipDict
import html

def zulipMarkdown(message):
    """
    Converts from slack specific markdown to zulip markdown.

    :param message: message from slack event
    :type message: string
    """

    boldCount, italicCount, strikeCount, multiBlockCount = 0,0,0,0
    message = html.unescape(message)

    # tokens used for simple markdown conversion
    tokens = [
        'EMOJI',
        'NEWLINE',
        'DOUBLELINE',
        'SINGLELINEBLOCK',
        'MULTILINEBLOCK',
        'SPACE',
        'BOLD',
        'ITALIC',
        'STRIKE',
        'LIST',
        'SIMPLELINK',
        'COMPLEXLINK',
        'STRING',
        'QUOTE',
        'BOLDITALIC',
    ]

    # token definitions using regular expressions
    t_EMOJI = r":[a-zA-Z\_\-]+:"
    t_NEWLINE = r"\n"
    t_DOUBLELINE = r"\n{2}"
    t_SINGLELINEBLOCK = r"\`[^` ][^`]+[^ ]\`"
    t_MULTILINEBLOCK = r"\`{3}"
    t_SPACE = r"[\t ]"
    t_BOLDITALIC = r"\*{3}"
    t_BOLD = r"\*"
    t_ITALIC = r"\_"
    t_STRIKE = r"\~"
    t_LIST = r"^[ ]*[\-\+\•\*][^\`\*\n]*$"
    t_SIMPLELINK = r"[\<][^<>|]+[\>]"
    t_COMPLEXLINK = r"[\<][^<>|]+\|[^<>|]+[\>]"
    t_QUOTE = r"^[ ]*\>[ ]*"
    t_STRING = r"."

    def t_error(t):
        pass

    lexer = lex.lex(reflags=re.MULTILINE)

    # The Slack message as input
    lexer.input(message)
    parsedList = []


    def parseToken(currentToken):
        """
        Returns a string based on the token type

        :param currentToken: Token object generated by the lexer
        :type currentToken: Token object
        """
        # get the type and value of the current token
        Ttype = currentToken['type']
        value = currentToken['value']

        # perform a certain action depending on the type of token
        if Ttype == 'BOLD':
            return "**"
        elif Ttype == 'EMOJI':
            if current_user.emojiAdditions == "{}":
                return slackToZulipDict().get(value, '')
            else:
                updateDict = slackToZulipDict()
                updateDict.update(json.loads(current_user.emojiAdditions))
                return updateDict.get(value, '')
        elif Ttype == 'ITALIC':
            return "*"
        elif Ttype == 'STRIKE':
            return "~~"
        elif Ttype == 'SIMPLELINK':
            try:
                # if the link captured is valid then return it
                if get(value[1:-1]).status_code == 200:
                    return value[1:-1]
            except:
                return value

        elif Ttype == 'COMPLEXLINK':
            # split at pipe
            pipe = value.find("|")
            beforePipe = value[1:pipe]
            afterPipe = value[pipe+1:-1]

            # if the before and after pipes are both urls e.g. <http://www.google.com|www.google.com>
            if beforePipe.startswith("https://") or beforePipe.startswith("http://"):
                    return "[" + zulipMarkdown(afterPipe)[0] + "]" + "(" + beforePipe + ")"

        elif Ttype == 'SPACE':
            return value
        elif Ttype == 'NEWLINE':
            return value
        elif Ttype == 'DOUBLELINE':
            return value
        elif Ttype == 'SINGLELINEBLOCK':
            return value
        elif Ttype == 'MULTILINEBLOCK':
            return value.strip()
        elif Ttype == 'QUOTE':
            return value.strip()
        elif Ttype == 'LIST':
            splitValue = zulipMarkdown(''.join(value.strip())[1:])[0]
            return '- ' + splitValue.strip()
        elif Ttype == 'STRING':
            return value
        elif Ttype == 'BOLDITALIC':
            if parsedList.count("*_") % 2 == 1:
                return "_*"
            return "*_"

        return ""

    tokenList = []

    while True:
        tok = lexer.token()
        if not tok:
            break

        # converts tok object into a simple dictionary, with key as the type and value as the value e.g. {'STRING', 'this is a string token'}
        tok = {'type': tok.__dict__['type'], 'value': tok.__dict__['value']}

        # count the number of occurrences of bold, italic and strike
        if tok['type'] == 'BOLD':
            boldCount += 1
        elif tok['type'] == 'ITALIC':
            italicCount += 1
        elif tok['type'] == 'STRIKE':
            strikeCount += 1
        elif tok['type'] == 'MULTILINEBLOCK':
            multiBlockCount += 1

        tokenList.append(tok)


    def santiseBoldItalicStrike(tokenType):
        '''
        Prevent bold, italic and strike tokens spanning multiple lines without being ended.

        :param tokenType: Name of token that requires sanitisation
        :type tokenType: String
        '''
        for _ in tokenList:
            sanitiseList = []
            for i, token in enumerate(tokenList):
                if token['type'] == tokenType: sanitiseList.append(i)
                if (token['type'] == 'NEWLINE' or i == len(tokenList) - 1) and len(sanitiseList) % 2 == 1 and len(sanitiseList) > 0:
                    tokenList[sanitiseList[-1]]['type'] = 'STRING'


    # remove multiline tokens as these aren't allowed in Slack or Zulip
    [santiseBoldItalicStrike(tokType) for tokType in ['BOLD', 'ITALIC', 'STRIKE', 'BOLDITALIC']]

    # Add newlines before and after the multiline block tags
    for count, tok in enumerate(tokenList):
        if tok['type'] == 'MULTILINEBLOCK':
            # if the token is a MULTILINEBLOCK then check to see if the next token is a newline
            if count + 1 < len(tokenList) and tokenList[count + 1]['type'] != "NEWLINE":
                tokenList.insert(count + 1, {'type': 'NEWLINE', 'value': '\n'})

                # for all token after the multi line block
                for i in range(count-1, len(tokenList)):
                    if tokenList[i]['type'] == 'MULTILINEBLOCK':
                        if tokenList[i-1]['type'] != 'NEWLINE':
                            tokenList.insert(i, {'type': 'NEWLINE', 'value': '\n'})
                        break

                    tokenList[i]['type'] = 'STRING'

    # Force new line to the beginning of the message
    if len(tokenList) > 1 and tokenList[0]['type'] in ['LIST', 'QUOTE', 'MULTILINEBLOCK']:
        tokenList.insert(0, {'type': 'NEWLINE', 'value': '\n'})

    # add each parsed token to a list to be returned
    for tok in tokenList:
        parsedList.append(parseToken(tok))

    # return a tuple with a Slack formatted message and a list of files
    return ''.join(parsedList), []
