# Copyright (c) 2021, Xhive Global Ltd. and Contributors
# MIT License. See LICENSE

import click

import xhiveframework
from xhiveframework import _


@xhiveframework.whitelist()
def download_pdf(doctype, name, print_format, letterhead=None):
	doc = xhiveframework.get_doc(doctype, name)
	doc.check_permission("print")
	generator = PrintFormatGenerator(print_format, doc, letterhead)
	pdf = generator.render_pdf()

	xhiveframework.local.response.filename = "{name}.pdf".format(
		name=name.replace(" ", "-").replace("/", "-")
	)
	xhiveframework.local.response.filecontent = pdf
	xhiveframework.local.response.type = "pdf"


def get_html(doctype, name, print_format, letterhead=None):
	doc = xhiveframework.get_doc(doctype, name)
	doc.check_permission("print")
	generator = PrintFormatGenerator(print_format, doc, letterhead)
	return generator.get_html_preview()


class PrintFormatGenerator:
	"""
	Generate a PDF of a Document, with repeatable header and footer if letterhead is provided.

	This generator draws its inspiration and, also a bit of its implementation, from this
	discussion in the library github issues: https://github.com/Kozea/WeasyPrint/issues/92
	"""

	def __init__(self, print_format, doc, letterhead=None):
		"""
		Parameters
		----------
		print_format: str
		        Name of the Print Format
		doc: str
		        Document to print
		letterhead: str
		        Letter Head to apply (optional)
		"""
		self.base_url = xhiveframework.utils.get_url()
		self.print_format = xhiveframework.get_doc("Print Format", print_format)
		self.doc = doc

		if letterhead == _("No Letterhead"):
			letterhead = None
		self.letterhead = xhiveframework.get_doc("Letter Head", letterhead) if letterhead else None

		self.build_context()
		self.layout = self.get_layout(self.print_format)
		self.context.layout = self.layout

	def build_context(self):
		self.print_settings = xhiveframework.get_doc("Print Settings")
		page_width_map = {"A4": 210, "Letter": 216}
		page_width = page_width_map.get(self.print_settings.pdf_page_size) or 210
		body_width = page_width - self.print_format.margin_left - self.print_format.margin_right
		print_style = (
			xhiveframework.get_doc("Print Style", self.print_settings.print_style)
			if self.print_settings.print_style
			else None
		)
		context = xhiveframework._dict(
			{
				"doc": self.doc,
				"print_format": self.print_format,
				"print_settings": self.print_settings,
				"print_style": print_style,
				"letterhead": self.letterhead,
				"page_width": page_width,
				"body_width": body_width,
			}
		)
		self.context = context

	def get_html_preview(self):
		header_html, footer_html = self.get_header_footer_html()
		self.context.header = header_html
		self.context.footer = footer_html
		return self.get_main_html()

	def get_main_html(self):
		self.context.css = xhiveframework.render_template(
			"templates/print_format/print_format.css", self.context
		)
		return xhiveframework.render_template("templates/print_format/print_format.html", self.context)

	def get_header_footer_html(self):
		header_html = footer_html = None
		if self.letterhead:
			header_html = xhiveframework.render_template("templates/print_format/print_header.html", self.context)
		if self.letterhead:
			footer_html = xhiveframework.render_template("templates/print_format/print_footer.html", self.context)
		return header_html, footer_html

	def render_pdf(self):
		"""
		Returns
		-------
		pdf: a bytes sequence
		        The rendered PDF.
		"""
		HTML, CSS = import_weasyprint()

		self._make_header_footer()

		self.context.update({"header_height": self.header_height, "footer_height": self.footer_height})
		main_html = self.get_main_html()

		html = HTML(string=main_html, base_url=self.base_url)
		main_doc = html.render()

		if self.header_html or self.footer_html:
			self._apply_overlay_on_main(main_doc, self.header_body, self.footer_body)
		pdf = main_doc.write_pdf()

		return pdf

	def _compute_overlay_element(self, element: str):
		"""
		Parameters
		----------
		element: str
		        Either 'header' or 'footer'

		Returns
		-------
		element_body: BlockBox
		        A Weasyprint pre-rendered representation of an html element
		element_height: float
		        The height of this element, which will be then translated in a html height
		"""
		HTML, CSS = import_weasyprint()

		html = HTML(
			string=getattr(self, f"{element}_html"),
			base_url=self.base_url,
		)
		element_doc = html.render(stylesheets=[CSS(string="@page {size: A4 portrait; margin: 0;}")])
		element_page = element_doc.pages[0]
		element_body = PrintFormatGenerator.get_element(element_page._page_box.all_children(), "body")
		element_body = element_body.copy_with_children(element_body.all_children())
		element_html = PrintFormatGenerator.get_element(element_page._page_box.all_children(), element)

		if element == "header":
			element_height = element_html.height
		if element == "footer":
			element_height = element_page.height - element_html.position_y

		return element_body, element_height

	def _apply_overlay_on_main(self, main_doc, header_body=None, footer_body=None):
		"""
		Insert the header and the footer in the main document.

		Parameters
		----------
		main_doc: Document
		        The top level representation for a PDF page in Weasyprint.
		header_body: BlockBox
		        A representation for an html element in Weasyprint.
		footer_body: BlockBox
		        A representation for an html element in Weasyprint.
		"""
		for page in main_doc.pages:
			page_body = PrintFormatGenerator.get_element(page._page_box.all_children(), "body")

			if header_body:
				page_body.children += header_body.all_children()
			if footer_body:
				page_body.children += footer_body.all_children()

	def _make_header_footer(self):
		self.header_html, self.footer_html = self.get_header_footer_html()

		if self.header_html:
			header_body, header_height = self._compute_overlay_element("header")
		else:
			header_body, header_height = None, 0
		if self.footer_html:
			footer_body, footer_height = self._compute_overlay_element("footer")
		else:
			footer_body, footer_height = None, 0

		self.header_body = header_body
		self.header_height = header_height
		self.footer_body = footer_body
		self.footer_height = footer_height

	def get_layout(self, print_format):
		layout = xhiveframework.parse_json(print_format.format_data)
		layout = self.set_field_renderers(layout)
		layout = self.process_margin_texts(layout)
		return layout

	def set_field_renderers(self, layout):
		renderers = {"HTML Editor": "HTML", "Markdown Editor": "Markdown"}
		for section in layout["sections"]:
			for column in section["columns"]:
				for df in column["fields"]:
					fieldtype = df["fieldtype"]
					renderer_name = fieldtype.replace(" ", "")
					df["renderer"] = renderers.get(fieldtype) or renderer_name
					df["section"] = section
		return layout

	def process_margin_texts(self, layout):
		margin_texts = [
			"top_left",
			"top_center",
			"top_right",
			"bottom_left",
			"bottom_center",
			"bottom_right",
		]
		for key in margin_texts:
			text = layout.get("text_" + key)
			if text and "{{" in text:
				layout["text_" + key] = xhiveframework.render_template(text, self.context)

		return layout

	@staticmethod
	def get_element(boxes, element):
		"""
		Given a set of boxes representing the elements of a PDF page in a DOM-like way, find the
		box which is named `element`.

		Look at the notes of the class for more details on Weasyprint insides.
		"""
		for box in boxes:
			if box.element_tag == element:
				return box
			return PrintFormatGenerator.get_element(box.all_children(), element)


def import_weasyprint():
	try:
		from weasyprint import CSS, HTML

		return HTML, CSS
	except OSError:
		message = "\n".join(
			[
				"WeasyPrint depdends on additional system dependencies.",
				"Follow instructions specific to your operating system:",
				"https://doc.courtbouillon.org/weasyprint/stable/first_steps.html",
			]
		)
		click.secho(message, fg="yellow")
		xhiveframework.throw(message)
