# a custom page to build and cache thumbnails of VO documents

import os
import subprocess
import sys
import tempfile
from io import BytesIO

from PIL import Image

from twisted.web import resource
from twisted.web import server
from twisted.web import static
from twisted.internet import threads

from gavo import base
from gavo import utils
from gavo import web

class CannotComputeThumbnail(Exception):
	pass


def pdfToJpeg(pdfData):
	"""returns bytes for a JFIF image from bytes containing a PDF stream.
	"""
	# we probably should sandbox this, given how crappy the PDF
	# engines are...  But then, who will attack us?
	# pdf2ppm falters when reading from plain stdin.  Let's have tempfile
	with tempfile.NamedTemporaryFile(suffix=".pdf") as tf:
		tf.write(pdfData)
		tf.flush()
		converter = subprocess.Popen([
			"pdftoppm", "-f",  "1", "-l", "1",
			'-singlefile', '-scale-to-x', '200',
			'-scale-to-y', '-1', '-jpeg', tf.name],
			stdin=subprocess.PIPE, stdout=subprocess.PIPE)
		jpegStream, _ = converter.communicate("")
		if converter.returncode:
			raise CannotComputeThumbnail("Converter failure (check stderr?)")
	return jpegStream


def htmlToPNG(url):
	"""returns a PNG literal for some HTML at url.
	"""
	fd, tmpname = tempfile.mkstemp(suffix=".png")
	os.close(fd)
	try:
		converter = subprocess.Popen([
			"xvfb-run", "-e", "/data/gavo/logs/vott-thumb.log", "-s", "-screen 0 800x1000x24", "--",
			"cutycapt", "--javascript=no", "--zoom-factor=1.5",
			"--insecure",
			"--url="+url, "--out="+tmpname],
			stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		out, err = converter.communicate("")
		if err and err.strip():
			base.ui.notifyWarning(
				f"While trying to create thumbnail for {url}: {err}")

		im = Image.open(tmpname)
		im = im.convert("RGB")
		scale = 200./im.size[0]
		im = im.resize((
			int(im.size[0]*scale),
			int(im.size[1]*scale)))
		if im.size[1]>288:
			im = im.crop((0,0,200,288))

		f = BytesIO()
		im.save(f, format="jpeg")
		return f.getvalue()
	finally:
		os.unlink(tmpname)


def _computeThumbnailData(ivoid):
	"""returns a bytestring containing a thumbnail for the document at ivoid.

	This will raise a CannotComputeThumbnail exception if no thumbnail
	can be built.
	"""
	try:
		with base.getTableConn() as conn:
			links = list(conn.query("SELECT access_url"
				" FROM rr.interface"
				" WHERE ivoid=%(ivoid)s"
				" AND intf_role='rendered'"
				" ORDER BY cap_index ASC LIMIT 1",
				{"ivoid": ivoid}))
			if not links:
				raise CannotComputeThumbnail("No document links found")

		try:
			remote_resource = utils.urlopenRemote(links[0][0])
		except IOError as msg:
			raise CannotComputeThumbnail("Cannot retrieve document: %s"%msg)

		media_type = remote_resource.headers["content-type"].split(";")[0]
		if media_type.endswith("/pdf"):
			return pdfToJpeg(remote_resource.read())

		elif media_type.endswith("html"):
			return htmlToPNG(links[0][0])
	except Exception as ex:
		base.ui.notifyError("Cannot compute thumbnail")
		raise CannotComputeThumbnail("Failed to compute thumbnail: {}"
			.format(ex))

	raise CannotComputeThumbnail("Can only build thumbnails for PDFs and HTML,"
		" not for %s"%media_type)


class Thumbnail(resource.Resource):
	"""computes and caches a thumbnail for a document or returns the cached copy.
	"""
	def __init__(self, thumbPath, rd, cacheDir):
		self.ivoid, self.rd = "ivo://"+thumbPath, rd
		self.cacheDir = cacheDir
		self.destFName = os.path.join(
			self.cacheDir,
			self.ivoid.replace("/", "=2f"))+".jpg"
		resource.Resource.__init__(self)

	def _computeThumbnail(self):
		"""computes and caches a thumbnail for self.ivoid.

		Well, actually, we're just figuring out a document access URL,
		and if it's a PDF we render the first page and return that.
		Else we're just returning the IVOA logo.

		We *could* get a lot more sophisticated here.
		"""
		try:
			thumbnail = _computeThumbnailData(self.ivoid)
		except CannotComputeThumbnail:
			with open(self.rd.getAbsPath("res/defaultThumbnail.jpg"), "rb") as f:
				thumbnail = f.read()

		with open(self.destFName, "wb") as f:
			f.write(thumbnail)
		return static.File(self.destFName)

	def render_GET(self, request):
		if os.path.exists(self.destFName):
			return static.File(self.destFName).render(request)
		threads.deferToThread(
			self._computeThumbnail).addCallback(
			lambda res: res.render(request))
		return server.NOT_DONE_YET


class MainPage(web.ServiceBasedPage):
	name = "custom"
	def __init__(self, *args, **kwargs):
		web.ServiceBasedPage.__init__(self, *args, **kwargs)
		self.cacheDir = os.path.join(
			base.getConfig("cachedir"),
			"vott_thumbnails")
		if not os.path.isdir(self.cacheDir):
			os.makedirs(self.cacheDir)

	def getChild(self, name, request):
		segments = request.popSegments(name)
		return Thumbnail("/".join(segments), self.rd, self.cacheDir)


if __name__=="__main__":
	import sys
	with open("tmp.jpg", "wb") as f:
		f.write(_computeThumbnailData(sys.argv[1]))
