from fontTools import ttLib
from fontTools.misc.textTools import safeEval
from fontTools.ttLib.tables.DefaultTable import DefaultTable
import sys
import os
import logging

log = logging.getLogger(__name__)


class TTXParseError(Exception):
    pass


BUFSIZE = 0x4000


class XMLReader(object):
    def __init__(
        self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False
    ):
        if fileOrPath == "-":
            fileOrPath = sys.stdin
        if not hasattr(fileOrPath, "read"):
            self.file = open(fileOrPath, "rb")
            self._closeStream = True
        else:
            # assume readable file object
            self.file = fileOrPath
            self._closeStream = False
        self.ttFont = ttFont
        self.progress = progress
        if quiet is not None:
            from fontTools.misc.loggingTools import deprecateArgument

            deprecateArgument("quiet", "configure logging instead")
            self.quiet = quiet
        self.root = None
        self.contentStack = []
        self.contentOnly = contentOnly
        self.stackSize = 0

    def read(self, rootless=False):
        if rootless:
            self.stackSize += 1
        if self.progress:
            self.file.seek(0, 2)
            fileSize = self.file.tell()
            self.progress.set(0, fileSize // 100 or 1)
            self.file.seek(0)
        self._parseFile(self.file)
        if self._closeStream:
            self.close()
        if rootless:
            self.stackSize -= 1

    def close(self):
        self.file.close()

    def _parseFile(self, file):
        from xml.parsers.expat import ParserCreate

        parser = ParserCreate()
        parser.StartElementHandler = self._startElementHandler
        parser.EndElementHandler = self._endElementHandler
        parser.CharacterDataHandler = self._characterDataHandler

        pos = 0
        while True:
            chunk = file.read(BUFSIZE)
            if not chunk:
                parser.Parse(chunk, 1)
                break
            pos = pos + len(chunk)
            if self.progress:
                self.progress.set(pos // 100)
            parser.Parse(chunk, 0)

    def _startElementHandler(self, name, attrs):
        if self.stackSize == 1 and self.contentOnly:
            # We already know the table we're parsing, skip
            # parsing the table tag and continue to
            # stack '2' which begins parsing content
            self.contentStack.append([])
            self.stackSize = 2
            return
        stackSize = self.stackSize
        self.stackSize = stackSize + 1
        subFile = attrs.get("src")
        if subFile is not None:
            if hasattr(self.file, "name"):
                # if file has a name, get its parent directory
                dirname = os.path.dirname(self.file.name)
            else:
                # else fall back to using the current working directory
                dirname = os.getcwd()
            subFile = os.path.join(dirname, subFile)
        if not stackSize:
            if name != "ttFont":
                raise TTXParseError("illegal root tag: %s" % name)
            if self.ttFont.reader is None and not self.ttFont.tables:
                sfntVersion = attrs.get("sfntVersion")
                if sfntVersion is not None:
                    if len(sfntVersion) != 4:
                        sfntVersion = safeEval('"' + sfntVersion + '"')
                    self.ttFont.sfntVersion = sfntVersion
            self.contentStack.append([])
        elif stackSize == 1:
            if subFile is not None:
                subReader = XMLReader(subFile, self.ttFont, self.progress)
                subReader.read()
                self.contentStack.append([])
                return
            tag = ttLib.xmlToTag(name)
            msg = "Parsing '%s' table..." % tag
            if self.progress:
                self.progress.setLabel(msg)
            log.info(msg)
            if tag == "GlyphOrder":
                tableClass = ttLib.GlyphOrder
            elif "ERROR" in attrs or ("raw" in attrs and safeEval(attrs["raw"])):
                tableClass = DefaultTable
            else:
                tableClass = ttLib.getTableClass(tag)
                if tableClass is None:
                    tableClass = DefaultTable
            if tag == "loca" and tag in self.ttFont:
                # Special-case the 'loca' table as we need the
                #    original if the 'glyf' table isn't recompiled.
                self.currentTable = self.ttFont[tag]
            else:
                self.currentTable = tableClass(tag)
                self.ttFont[tag] = self.currentTable
            self.contentStack.append([])
        elif stackSize == 2 and subFile is not None:
            subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
            subReader.read()
            self.contentStack.append([])
            self.root = subReader.root
        elif stackSize == 2:
            self.contentStack.append([])
            self.root = (name, attrs, self.contentStack[-1])
        else:
            l = []
            self.contentStack[-1].append((name, attrs, l))
            self.contentStack.append(l)

    def _characterDataHandler(self, data):
        if self.stackSize > 1:
            # parser parses in chunks, so we may get multiple calls
            # for the same text node; thus we need to append the data
            # to the last item in the content stack:
            # https://github.com/fonttools/fonttools/issues/2614
            if (
                data != "\n"
                and self.contentStack[-1]
                and isinstance(self.contentStack[-1][-1], str)
                and self.contentStack[-1][-1] != "\n"
            ):
                self.contentStack[-1][-1] += data
            else:
                self.contentStack[-1].append(data)

    def _endElementHandler(self, name):
        self.stackSize = self.stackSize - 1
        del self.contentStack[-1]
        if not self.contentOnly:
            if self.stackSize == 1:
                self.root = None
            elif self.stackSize == 2:
                name, attrs, content = self.root
                self.currentTable.fromXML(name, attrs, content, self.ttFont)
                self.root = None


class ProgressPrinter(object):
    def __init__(self, title, maxval=100):
        print(title)

    def set(self, val, maxval=None):
        pass

    def increment(self, val=1):
        pass

    def setLabel(self, text):
        print(text)
