import re
import xml.sax.saxutils
from pkgutil import get_data
from string import Template
from typing import Optional

from ebooklib import epub

from gourmand import gglobals
from gourmand.exporters.exporter import ExporterMultirec, exporter_mult
from gourmand.i18n import _

RECIPE_HEADER = Template(
    """<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>$title</title>
</head>
<body>
"""
)
RECIPE_FOOT = "</body></html>"


class EpubWriter:
    """This class contains all things to write an epub and is a small wrapper
    around the EbookLib which is capable of producing epub files and maybe
    kindle in the future (it is under heavy development).
    """

    _default_style = "epubdefault.css"

    def __init__(self, outFileName):
        """
        @param outFileName The filename + path the ebook is written to on finish.
        """
        self.outFileName = outFileName
        self.lang = "en"
        self.recipeCss = None

        self.imgCount = 0
        self.recipeCount = 0

        self.ebook = epub.EpubBook()
        self.spine = ["nav"]
        self.toc = []

        # set metadata
        self.ebook.set_identifier("Cookme")  # TODO: Something meaningful or time?
        self.ebook.set_title("My Cookbook")
        self.ebook.set_language(self.lang)

        # This is normally the publisher, makes less sense for the moment
        # ebook.add_metadata('DC', 'publisher', "Gourmet")

        # TODO: Add real author from somewhere
        self.ebook.add_author("Gourmand")

        # This adds the field also known as keywords in some programs.
        self.ebook.add_metadata("DC", "subject", "cooking")

    def addRecipeCssFromFile(self, filename: Optional[str] = None) -> str:
        """Adds the CSS file from filename to the book. The style will be added
        to the books root.
        @param filename The file the css is read from and attached
        @return The internal name within the ebook to reference this file.
        """
        cssFileName = "Style/recipe.css"

        if filename:
            with open(filename, "rb") as fh:
                style = fh.read()
        else:
            style = get_data("gourmand", f"data/style/{self._default_style}")
        assert style
        recipe_css = epub.EpubItem(uid="style", file_name=cssFileName, media_type="text/css", content=style)
        self.ebook.add_item(recipe_css)
        self.recipeCss = recipe_css
        return cssFileName

    def addJpegImage(self, imageData):
        """Adds a jpeg image from the imageData array to the book and returns
        the reference name for the image to be used in html.
        @param imageData Image data in format jpeg
        @return The name of the image to be used in html
        """
        epimg = epub.EpubImage()
        epimg.file_name = "grf/image_%i.jpg" % self.imgCount
        self.imgCount += 1
        epimg.media_type = "image/jpeg"
        epimg.set_content(imageData)
        self.ebook.add_item(epimg)
        return epimg.file_name

    def getFileForRecipeID(self, id, ext=".xhtml"):
        """
        Returns a filename to reference a specific recipe
        @param id The id which is also passed during addRecipeText
        @return A filename for reference
        """
        return "recipe_%i%s" % (id, ext)

    def addRecipeText(self, uniqueId, title, text):
        """Adds the recipe text as a chapter."""
        uniqueName = self.getFileForRecipeID(uniqueId, ext="")
        fileName = self.getFileForRecipeID(uniqueId)
        self.recipeCount += 1

        c1 = epub.EpubHtml(title=title, file_name=fileName, lang=self.lang)
        c1.content = text.encode("utf-8")
        c1.add_item(self.recipeCss)

        # add chapter
        self.ebook.add_item(c1)
        self.spine.append(c1)

        # define Table Of Contents
        self.toc.append(epub.Link(fileName, title, uniqueName))

    def finish(self):
        """Finish the book and writes it to the disk."""
        self.ebook.toc = self.toc

        # add default NCX and Nav file
        self.ebook.add_item(epub.EpubNcx())
        self.ebook.add_item(epub.EpubNav())

        self.ebook.spine = self.spine

        epub.write_epub(self.outFileName, self.ebook, {})


class epub_exporter(exporter_mult):
    def __init__(
        self,
        rd,
        r,
        out,
        conv=None,
        doc=None,
        # exporter_mult args
        mult=1,
        change_units=True,
    ):
        self.doc = doc

        # This document will be appended by the strings to join them in the
        # last step and pass it to the ebookwriter.
        self.preparedDocument = []

        # self.link_generator=link_generator
        exporter_mult.__init__(self, rd, r, out, conv=conv, imgcount=1, mult=mult, change_units=change_units, do_markup=True, use_ml=True)

    def htmlify(self, text):
        t = text.strip()
        # t=xml.sax.saxutils.escape(t)
        t = "<p>%s</p>" % t
        t = re.sub("\n\n+", "</p><p>", t)
        t = re.sub("\n", "<br>", t)
        return t

    def get_title(self):
        """Returns the title of the book in an unescaped format"""
        title = self._grab_attr_(self.r, "title")
        if not title:
            title = _("Recipe")
        return str(title)

    def write_head(self):
        self.preparedDocument.append(RECIPE_HEADER.substitute(title=self.get_title()))
        self.preparedDocument.append("<h1>%s</h1>" % xml.sax.saxutils.escape(self.get_title()))

    def write_image(self, image):
        imagePath = self.doc.addJpegImage(image)
        self.preparedDocument.append('<img src="%s" itemprop="image"/>' % imagePath)

    def write_inghead(self):
        self.preparedDocument.append('<div class="ing"><h2>%s</h2><ul class="ing">' % _("Ingredients"))

    def write_text(self, label, text):
        attr = gglobals.NAME_TO_ATTR.get(label, label)
        if attr == "instructions":
            self.preparedDocument.append(
                '<div class="%s"><h2 class="%s">%s</h2><div itemprop="recipeInstructions">%s</div></div>' % (attr, label, label, self.htmlify(text))
            )
        else:
            self.preparedDocument.append('<div class="%s"><h2 class="%s">%s</h2>%s</div>' % (attr, label, label, self.htmlify(text)))

    def handle_italic(self, chunk):
        return "<em>" + chunk + "</em>"

    def handle_bold(self, chunk):
        return "<strong>" + chunk + "</strong>"

    def handle_underline(self, chunk):
        return "<u>" + chunk + "</u>"

    def write_attr_head(self):
        self.preparedDocument.append("<div class='header'>")

    def write_attr(self, label, text):
        attr = gglobals.NAME_TO_ATTR.get(label, label)
        if attr == "link":
            webpage = text.strip("http://")
            webpage = webpage.split("/")[0]
            self.preparedDocument.append('<a href="%s">' % text + _("Original Page from %s") % webpage + "</a>\n")
        elif attr == "rating":
            rating, rest = text.split("/", 1)
            self.preparedDocument.append(
                (
                    '<p class="%s" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">'
                    '<span class="label">%s:</span> <span itemprop="ratingValue">%s</span><span>/%s</span></p>\n'
                )
                % (attr, label.capitalize(), rating, rest)
            )
        else:
            itemprop = None
            if attr == "title":
                itemprop = "name"
                return  # Title is already printed
            elif attr == "category":
                itemprop = "recipeCategory"
            elif attr == "cuisine":
                itemprop = "recipeCuisine"
            elif attr == "yields":
                itemprop = "recipeYield"
            elif attr == "preptime":
                itemprop = "prepTime"
            elif attr == "cooktime":
                itemprop = "cookTime"
            elif attr == "instructions":
                itemprop = "recipeInstructions"
            if itemprop:
                self.preparedDocument.append(
                    '<p class="%s"><span class="label">%s:</span> <span itemprop="%s">%s</span></p>\n'
                    % (attr, label.capitalize(), itemprop, xml.sax.saxutils.escape(text))
                )
            else:
                self.preparedDocument.append(
                    "<p class='%s'><span class='label'>%s:</span> %s</p>\n" % (attr, label.capitalize(), xml.sax.saxutils.escape(text))
                )

    def write_attr_foot(self):
        self.preparedDocument.append("</div>")

    def write_grouphead(self, name):
        self.preparedDocument.append("<h3 class='inggroup'>%s<h3/>" % name)

    def write_groupfoot(self):
        pass

    def _write_ing_impl(self, amount, unit, item, link, optional):
        self.preparedDocument.append('<li class="ing" itemprop="ingredients">')

        # Escape all incoming things first.
        (amount, unit, item) = tuple([xml.sax.saxutils.escape("%s" % o) if o else "" for o in [amount, unit, item]])

        self.preparedDocument.append('<div class="ingamount">%s</div>' % (amount if len(amount) != 0 else "&nbsp;"))
        self.preparedDocument.append('<div class="ingunit">%s</div>' % (unit if len(unit) != 0 else "&nbsp;"))

        if item:
            if link:
                self.preparedDocument.append("<a href='%s'>%s</a>" % (link, item))
            else:
                self.preparedDocument.append(item)

        if optional:
            self.preparedDocument.append("(%s)" % _("optional"))
        self.preparedDocument.append("</li>\n")

    def write_ingref(self, amount, unit, item, refid, optional):
        refFile = self.doc.getFileForRecipeID(refid)
        self._write_ing_impl(amount, unit, item, refFile, optional)

    def write_ing(self, amount=1, unit=None, item=None, key=None, optional=False):
        self._write_ing_impl(amount, unit, item, None, optional)

    def write_ingfoot(self):
        self.preparedDocument.append("</ul>\n</div>\n")

    def write_foot(self):
        self.preparedDocument.append(RECIPE_FOOT)

        self._grab_attr_(self.r, "id")
        self.doc.addRecipeText(self._grab_attr_(self.r, "id"), self.get_title(), "".join(self.preparedDocument))


class website_exporter(ExporterMultirec):
    def __init__(
        self,
        rd,
        recipe_table,
        out,
        conv=None,
        ext="epub",
        copy_css=True,
        css: Optional[str] = None,
        index_rows=["title", "category", "cuisine", "rating", "yields"],
        change_units=False,
        mult=1,
    ):

        self.doc = EpubWriter(out)
        self.doc.addRecipeCssFromFile(css)

        self.ext = ext

        self.index_rows = index_rows
        self.exportargs = {"change_units": change_units, "mult": mult, "doc": self.doc}

        if conv:
            self.exportargs["conv"] = conv
        ExporterMultirec.__init__(
            self, rd, recipe_table, out, one_file=True, create_file=False, ext=self.ext, exporter=epub_exporter, exporter_kwargs=self.exportargs
        )

    def recipe_hook(self, rec, filename, exporter):
        """Add index entry"""
        # TODO: Do some cool things here.
        pass

    def write_footer(self):
        self.doc.finish()
