# GFM table, non-standard
import re

from .state_block import StateBlock
from ..common.utils import isSpace, charCodeAt


headerLineRe = re.compile(r"^:?-+:?$")
enclosingPipesRe = re.compile(r"^\||\|$")


def getLine(state: StateBlock, line: int):
    pos = state.bMarks[line] + state.blkIndent
    maximum = state.eMarks[line]

    # return state.src.substr(pos, max - pos)
    return state.src[pos:maximum]


def escapedSplit(string):
    result = []
    pos = 0
    max = len(string)
    escapes = 0
    lastPos = 0
    backTicked = False
    lastBackTick = 0
    ch = charCodeAt(string, pos)

    while pos < max:
        if ch == 0x60:  # /* ` */
            if backTicked:
                # make \` close code sequence, but not open it;
                # the reason is: `\` is correct code block
                backTicked = False
                lastBackTick = pos
            elif escapes % 2 == 0:
                backTicked = True
                lastBackTick = pos
        # /* | */
        elif ch == 0x7C and (escapes % 2 == 0) and not backTicked:
            result.append(string[lastPos:pos])
            lastPos = pos + 1

        if ch == 0x5C:  # /* \ */
            escapes += 1
        else:
            escapes = 0

        pos += 1

        # If there was an un-closed backtick, go back to just after
        # the last backtick, but as if it was a normal character
        if pos == max and backTicked:
            backTicked = False
            pos = lastBackTick + 1

        ch = charCodeAt(string, pos)

    result.append(string[lastPos:])

    return result


def table(state: StateBlock, startLine: int, endLine: int, silent: bool):

    # should have at least two lines
    if startLine + 2 > endLine:
        return False

    nextLine = startLine + 1

    if state.sCount[nextLine] < state.blkIndent:
        return False

    # if it's indented more than 3 spaces, it should be a code block
    if state.sCount[nextLine] - state.blkIndent >= 4:
        return False

    # first character of the second line should be '|', '-', ':',
    # and no other characters are allowed but spaces;
    # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp

    pos = state.bMarks[nextLine] + state.tShift[nextLine]
    if pos >= state.eMarks[nextLine]:
        return False

    ch = state.srcCharCode[pos]
    pos += 1
    # /* | */ /* - */ /* : */
    if ch != 0x7C and ch != 0x2D and ch != 0x3A:
        return False

    while pos < state.eMarks[nextLine]:
        ch = state.srcCharCode[pos]

        # /* | */  /* - */ /* : */
        if ch != 0x7C and ch != 0x2D and ch != 0x3A and not isSpace(ch):
            return False

        pos += 1

    lineText = getLine(state, startLine + 1)

    columns = lineText.split("|")
    aligns = []
    for i in range(len(columns)):
        t = columns[i].strip()
        if not t:
            # allow empty columns before and after table, but not in between columns;
            # e.g. allow ` |---| `, disallow ` ---||--- `
            if i == 0 or i == len(columns) - 1:
                continue
            else:
                return False

        if not headerLineRe.search(t):
            return False
        if charCodeAt(t, len(t) - 1) == 0x3A:  # /* : */
            # /* : */
            aligns.append("center" if charCodeAt(t, 0) == 0x3A else "right")
        elif charCodeAt(t, 0) == 0x3A:  # /* : */
            aligns.append("left")
        else:
            aligns.append("")

    lineText = getLine(state, startLine).strip()
    if "|" not in lineText:
        return False
    if state.sCount[startLine] - state.blkIndent >= 4:
        return False
    columns = escapedSplit(enclosingPipesRe.sub("", lineText))

    # header row will define an amount of columns in the entire table,
    # and align row shouldn't be smaller than that (the rest of the rows can)
    columnCount = len(columns)
    if columnCount > len(aligns):
        return False

    if silent:
        return True

    token = state.push("table_open", "table", 1)
    token.map = tableLines = [startLine, 0]

    token = state.push("thead_open", "thead", 1)
    token.map = [startLine, startLine + 1]

    token = state.push("tr_open", "tr", 1)
    token.map = [startLine, startLine + 1]

    for i in range(len(columns)):
        token = state.push("th_open", "th", 1)
        token.map = [startLine, startLine + 1]
        if aligns[i]:
            token.attrs = [["style", "text-align:" + aligns[i]]]

        token = state.push("inline", "", 0)
        token.content = columns[i].strip()
        token.map = [startLine, startLine + 1]
        token.children = []

        token = state.push("th_close", "th", -1)

    token = state.push("tr_close", "tr", -1)
    token = state.push("thead_close", "thead", -1)

    token = state.push("tbody_open", "tbody", 1)
    token.map = tbodyLines = [startLine + 2, 0]

    nextLine = startLine + 2
    while nextLine < endLine:
        if state.sCount[nextLine] < state.blkIndent:
            break

        lineText = getLine(state, nextLine).strip()
        if "|" not in lineText:
            break
        if state.sCount[nextLine] - state.blkIndent >= 4:
            break
        columns = escapedSplit(enclosingPipesRe.sub("", lineText))

        token = state.push("tr_open", "tr", 1)
        for i in range(columnCount):
            token = state.push("td_open", "td", 1)
            token.map = [nextLine, nextLine + 1]
            if aligns[i]:
                token.attrs = [["style", "text-align:" + aligns[i]]]

            token = state.push("inline", "", 0)
            token.map = [nextLine, nextLine + 1]
            try:
                token.content = columns[i].strip() if columns[i] else ""
            except IndexError:
                token.content = ""
            token.children = []

            token = state.push("td_close", "td", -1)

        token = state.push("tr_close", "tr", -1)

        nextLine += 1

    token = state.push("tbody_close", "tbody", -1)
    token = state.push("table_close", "table", -1)

    tableLines[1] = tbodyLines[1] = nextLine
    state.line = nextLine
    return True
