
import re
import bpy
from .EditTextError import *
_lowercase = re.compile("[a-z]")
_all_whitespace = re.compile("^\\s*$")


def to_char(text):
    # type: (str) -> str
    return text+".png"


def parse_char(i):
    # type: (str) -> bpy.ParsedResult
    # Generated by imageToCharStringConverter/cli/src/createFont.ts
    # do not modify by hand
    if i == "\\":
        return None
    if i == " ":
        return "space"
    if _lowercase.match(i) is not None:
        return to_char(i.upper())
    if i.isupper():
        return to_char("capital"+i)
    if i == "0":
        return to_char("zero")
    if i == "1":
        return to_char("one")
    if i == "2":
        return to_char("two")
    if i == "3":
        return to_char("three")
    if i == "4":
        return to_char("four")
    if i == "5":
        return to_char("five")
    if i == "6":
        return to_char("six")
    if i == "7":
        return to_char("seven")
    if i == "8":
        return to_char("eight")
    if i == "9":
        return to_char("nine")
    if i == "!":
        return to_char("exclam")
    if i == "\"":
        return to_char("quotedbl")
    if i == "#":
        return to_char("numbersign")
    if i == "$":
        return to_char("dollar")
    if i == "%":
        return to_char("percent")
    if i == "&":
        return to_char("ampersand")
    if i == "'":
        return to_char("quotesingle")
    if i == "(":
        return to_char("parenleft")
    if i == ")":
        return to_char("parenright")
    if i == "*":
        return to_char("asterisk")
    if i == "+":
        return to_char("plus")
    if i == ",":
        return to_char("comma")
    if i == "-":
        return to_char("hyphen")
    if i == ".":
        return to_char("period")
    if i == "/":
        return to_char("slash")
    if i == ":":
        return to_char("colon")
    if i == ";":
        return to_char("semicolon")
    if i == "<":
        return to_char("less")
    if i == "=":
        return to_char("equal")
    if i == ">":
        return to_char("greater")
    if i == "?":
        return to_char("question")
    if i == "@":
        return to_char("at")
    if i == "[":
        return to_char("bracketleft")
    if i == "]":
        return to_char("bracketright")
    if i == "^":
        return to_char("asciicircum")
    if i == "_":
        return to_char("underscore")
    if i == "`":
        return to_char("grave")
    if i == "{":
        return to_char("braceleft")
    if i == "|":
        return to_char("bar")
    if i == "}":
        return to_char("braceright")
    if i == "~":
        return to_char("asciitilde")
    if i == "Â©":
        return to_char("copyright")
    # end generated

    raise Exception("Unrecognized character in text: " + i)


class ParseState:
    def __init__(self):
        self.parsed_text = []  # type: list[list[str]]
        self.current_line = []  # type: list[str]
        self.mightBeNewLine = False

    def get_mightBeNewLine(self):
        # type: () -> bool
        return self.mightBeNewLine

    def set_mightBeNewLine(self, m):
        # type: (bool) -> None
        self.mightBeNewLine = m

    def addLine(self):
        # type: () -> None
        self.parsed_text.append(self.current_line)
        self.current_line = []

    def addChar(self, ascii_character, char, width, skip_space=False):
        # type: (str, str, int, bool) -> None
        self.current_line.append(char)
        if len(self.current_line) < width:
            return
        self.addLine()

    def shouldSkipDoubleNewline(self, width):
        # type: (int) -> bool
        if len(self.current_line) > 0:
            return False
        length = len(self.parsed_text)
        if length < 1:
            return False
        line_above = self.parsed_text[length - 1]
        return len(line_above) == width

    def currentLineIsEmpty(self):
        # type: () -> bool
        return len(self.current_line) <= 0

    def number_of_frames(self):
        # type: () -> int
        number_of_frames = 0
        for line in self.parsed_text:
            number_of_frames += len(line)
        return number_of_frames


class WordParseState:
    def __init__(self):
        self.parsed_text = []  # type: list[list[bpy.WordInfo]]
        self.current_line = []  # type: list[bpy.WordInfo]
        self.current_word = []  # type: list[str]
        self.current_ascii_word = ""
        self.mightBeNewLine = False

    def get_mightBeNewLine(self):
        # type: () -> bool
        return self.mightBeNewLine

    def set_mightBeNewLine(self, m):
        # type: (bool) -> None
        self.mightBeNewLine = m

    def addLine(self):
        # type: () -> None
        if len(self.current_word) > 0:
            self.add_word_to_line_and_reset_word()
        self.parsed_text.append(self.current_line)
        self.current_line = []

    def lineWidth(self):
        # type: () -> int
        length = self.lineLength(self.current_line)
        # space before current word
        length += 1 if len(self.current_line) > 0 and len(self.current_word) > 0 else 0
        length += len(self.current_word)
        return length

    def lineLength(self, line):
        # type: (list[bpy.WordInfo]) -> int
        length = 0  # type: int
        for word in line:
            length += len(word)
        # spaces betwen words
        length += len(line) - 1
        return length

    def addChar(self, ascii_character, char, width, skip_space=True):
        # type: (str, str, int, bool) -> None
        if skip_space and (char == " " or char == "space"):
            self.add_word_to_line_and_reset_word()
            return
        self.current_word.append(char)
        self.current_ascii_word += ascii_character
        if self.lineWidth() < width:
            return
        self.addLine()

    def add_word_to_line_and_reset_word(self):
        self.current_line.append({
                "word": self.current_word,
                "ascii_word": self.current_ascii_word
        })
        self.current_word = []
        self.current_ascii_word = ""

    def shouldSkipDoubleNewline(self, width):
        # type: (int) -> bool
        if len(self.current_line) > 0 or len(self.current_word) > 0:
            return False
        if len(self.parsed_text) < 1:
            return False
        line_above = self.parsed_text[len(self.parsed_text) - 1]
        return self.lineLength(line_above) == width

    def currentLineIsEmpty(self):
        # type: () -> bool
        return len(self.current_word) <= 0 and len(self.current_line) <= 0

    def number_of_frames(self):
        # type: () -> int
        number_of_frames = 0
        for line in self.parsed_text:
            number_of_frames += len(line)
        return number_of_frames


def parse_text(text, width, mutable_state):
    # type: (str, int, bpy.ParserType) -> None
    if len(text) <= 0 or _all_whitespace.match(text) is not None:
        raise EditTextError("Font text is empty!")
    if width <= 0:
        raise EditTextError("Text width is less than or equal to 1!")
    for i in text:
        if mutable_state.get_mightBeNewLine():
            mutable_state.set_mightBeNewLine(False)
            if i == "n":
                if mutable_state.shouldSkipDoubleNewline(width):
                    continue
                mutable_state.addLine()
                continue
            elif i == "\\":
                mutable_state.addChar("\\", to_char("backslash"), width, True)
                continue
            elif i == " ":
                mutable_state.addChar(" ", "space", width, False)
                continue
            raise Exception(
                "Expected a n for a newline character but found: " + i)
        parsed_char = parse_char(i)
        if parsed_char is None:
            # not a character must be a backslash
            mutable_state.set_mightBeNewLine(True)
            continue
        mutable_state.addChar(i, parsed_char, width, True)
    if not mutable_state.currentLineIsEmpty():
        mutable_state.addLine()
