import os.path
import re
import xml.sax

import gourmand.importers.importer as importer
import gourmand.importers.xml_importer as xml_importer
from gourmand.gdebug import debug
from gourmand.i18n import _
from gourmand.importers.xml_importer import unquoteattr


class Mx2Cleaner:
    def __init__(self):
        self.regs_to_toss = [r"<\?xml[^?]+\?>", "<!DOCTYPE[^>]+>"]
        self.toss_regexp = "("
        for r in self.regs_to_toss:
            self.toss_regexp = self.toss_regexp + r + "|"
        self.toss_regexp = self.toss_regexp[0:-1] + ")"
        self.toss_regexp = re.compile(self.toss_regexp)
        self.attr_regexp = '(<[^>]+?)"([^=]+["<>][^=]+)"'
        self.attr_regexp = re.compile(self.attr_regexp)
        self.encodings = ["cp1252", "iso8859", "ascii", "latin_1", "cp850", "utf-8"]

    def cleanup(self, infile, outfile):
        infile = open(infile, "rb")
        outfile = open(outfile, "w", encoding="utf-8")
        for line in infile.readlines():
            line = self.decode(line)
            line = self.toss_regs(line)
            line = self.fix_attrs(line)
            outfile.write(line)
        infile.close()
        outfile.close()

    def toss_regs(self, instr):
        m = self.toss_regexp.search(instr)
        if m:
            outstr = instr[0 : m.start()] + instr[m.end() :]
            debug('Converted "%s" to "%s"' % (instr, outstr), 1)
            return outstr
        else:
            return instr

    def fix_attrs(self, instr):
        match = self.attr_regexp.search(instr)
        outstr = ""
        while match:
            outstr = outstr + instr[0 : match.start()]
            pre, badattr = match.groups()
            outstr = outstr + pre
            outstr = outstr + xml.sax.saxutils.quoteattr(badattr)
            debug("Fixed broken attribute: %s -> %s" % (instr, outstr), 0)
            instr = instr[match.end() :]
            match = self.attr_regexp.search(instr)
        outstr = outstr + instr
        return outstr

    def decode(self, line: bytes) -> str:
        """Try several encodings, return the line once it's succesfully decoded"""
        for e in self.encodings:
            try:
                return line.decode(e)
            except UnicodeDecodeError:
                debug("Could not decode as %s" % e, 2)
                pass


class MastercookXMLHandler(xml_importer.RecHandler):
    """We handle MasterCook XML Files"""

    def __init__(self, parent_thread=None, conv=None):
        debug("MastercookXMLHandler starting", 0)
        xml_importer.RecHandler.__init__(self, parent_thread=parent_thread, conv=None)
        self.total = 0
        self.recs_done = 0
        self.elements = {
            "mx2": ["source", "date"],
            #'Summ':[],
            "Nam": [],
            "RcpE": ["name"],
            "RTxt": [],
            "Serv": ["qty"],
            "PropT": ["elapsed"],
            "IngR": ["name", "unit", "qty"],
            "IPrp": [],
            #'DirS':[],
            "DirT": [],
            "Desc": [],
            "Srce": [],
            "Note": [],
            "CatT": [],
            "Yield": ["unit", "qty"],
        }
        self.current_elements = []
        self.bufs = []
        xml.sax.ContentHandler.__init__(self)
        importer.Importer.__init__(self, conv=conv)

    def grabattr(self, attrs, name, default=""):
        return unquoteattr(attrs.get(name, default))

    def startElement(self, name, attrs):
        self.in_mixed = 0
        if name not in self.elements:
            debug("Unhandled element: %s" % name, 0)
            return
        else:
            self.current_elements = [name] + self.current_elements
            handler = self._get_handler(name)
            handler(start=True, attrs=attrs)

    def endElement(self, name):
        if name not in self.elements:
            return
        else:
            self.current_elements.remove(name)
            handler = self._get_handler(name)
            handler(end=True)

    def endDocument(self):
        self.emit("progress", 1, _("Mastercook import finished."))

    def _get_handler(self, name):
        return getattr(self, "%s_handler" % name)

    def mx2_handler(self, start=False, end=False, attrs=None):
        if start:
            pass

    def characters(self, ch):
        if self.bufs:
            debug("adding to %s bufs: %s" % (len(self.bufs), ch), 0)
        for buf in self.bufs:
            setattr(self, buf, getattr(self, buf) + ch)

    def Nam_handler(self, start=False, end=False, attrs=None):
        if start:
            # we simply count recipes so that we can
            # indicate progress.
            self.total += 1

    def RcpE_handler(self, start=False, end=False, attrs=None):
        if start:
            self.start_rec()
            # if self.source:
            #    self.rec['source']=self.source
            if attrs:
                self.rec["title"] = self.grabattr(attrs, "name")
        if end:
            if "yield" in self.rec:
                self._add_to_instructions("\nYield: %s %s" % self.rec["yield"])
                del self.rec["yield"]
            self.commit_rec()

    def RTxt_handler(self, start=False, end=False, attrs=None):
        if start:
            self.cdata_buf = ""
            self.bufs.append("cdata_buf")
        if end:
            self.bufs.remove("cdata_buf")

    def Serv_handler(self, start=False, end=False, attrs=None):
        if attrs:
            self.rec["servings"] = self.grabattr(attrs, "qty")

    def Yield_handler(self, start=False, end=False, attrs=None):
        if attrs:
            self.rec["yield"] = (self.grabattr(attrs, "qty"), self.grabattr(attrs, "unit"))

    def CatT_handler(self, start=False, end=False, attrs=None):
        if start:
            self.catbuf = ""
            self.bufs.append("catbuf")
        if end:
            self.bufs.remove("catbuf")
            self.catbuf = self.catbuf.strip()
            if "category" in self.rec:
                self.rec["category"] = self.rec["category"] + " " + self.catbuf
            else:
                self.rec["category"] = xml.sax.saxutils.unescape(self.catbuf)

    def IngR_handler(self, start=False, end=False, attrs=None):
        if attrs:
            self.start_ing()
            self.item = self.grabattr(attrs, "name")
            self.add_amt(self.grabattr(attrs, "qty"))
            self.ing["unit"] = self.grabattr(attrs, "unit")

        if end:
            if self.item.find("===") == 0:
                self.group = self.item[4:-4]
            else:
                self.add_item(self.item)
                debug("item=%s, unit=%s" % (self.item, self.ing["unit"]), 0)
                self.commit_ing()

    def _add_to_instructions(self, buf):
        debug("adding to instructions: %s" % buf, 0)
        if "instructions" in self.rec:
            self.rec["instructions"] = self.rec["instructions"] + "\n%s" % xml.sax.saxutils.unescape(buf)
        else:
            self.rec["instructions"] = xml.sax.saxutils.unescape(buf)

    def DirT_handler(self, start=False, end=False, attrs=None):
        if start:
            self.dbuf = ""
            self.bufs.append("dbuf")
        if end:
            self.bufs.remove("dbuf")
            self._add_to_instructions(self.dbuf.strip())

    # this also gets added to instructions
    Desc_handler = DirT_handler

    def Note_handler(self, start=False, end=False, attrs=None):
        if start:
            self.dbuf = ""
            self.bufs.append("dbuf")
        if end:
            self.bufs.remove("dbuf")
            buf = xml.sax.saxutils.unescape(self.dbuf.strip())
            if "modifications" in self.rec:
                self.rec["modifications"] = self.rec["modifications"] + "\n%s" % buf
            else:
                self.rec["modifications"] = buf

    def IPrp_handler(self, start=False, end=False, attrs=None):
        if start:
            self.ipbuf = ""
            self.bufs.append("ipbuf")
        if end:
            self.item += "; %s" % xml.sax.saxutils.unescape(self.ipbuf.strip())
            self.bufs.remove("ipbuf")

    def Srce_handler(self, start=False, end=False, attrs=None):
        if start:
            self.srcbuf = ""
            self.bufs.append("srcbuf")
        if end:
            self.rec["source"] = self.srcbuf.strip()
            self.bufs.remove("srcbuf")


class MastercookImporter(xml_importer.Converter):
    def __init__(self, filename):
        xml_importer.Converter.__init__(
            self,
            recHandler=MastercookXMLHandler,
            recMarker="<RcpE",
            filename=filename,
        )

    def pre_run(self):
        self.emit("progress", 0.03, _("Tidying up XML"))
        cleaner = Mx2Cleaner()
        base, ext = os.path.splitext(self.fn)
        cleanfn = base + ".gourmetcleaned" + ext
        cleaner.cleanup(self.fn, cleanfn)
        debug("Cleaned up file saved to %s" % cleanfn, 1)
        self.orig_fn = self.fn
        self.fn = cleanfn
