"""
Renderer not reacting too strongly on user input.

There's StaticRender just delivering certain files from within a service,
and there's FixedPageRenderer that just formats a defined template.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


import os

from twisted.web import server
from twisted.web import static

from gavo import base
from gavo import svcs
from gavo.web import grend
from gavo.web import ifpages


class StaticRenderer(grend.ServiceBasedPage):
	"""A renderer that just hands through files.

	The standard operation here is to set a staticData property pointing
	to a resdir-relative directory used to serve files for.  Indices
	for directories are created.

	You can define a root resource by giving an indexFile property on
	the service.  Note in particular that you can use an index file
	with an extension of shtml.  This lets you use nevow templates, but
	since metadata will be taken from the global context, that's
	probably not terribly useful.  You are probably looking for the fixed
	renderer if you find yourself needing this.
	"""
	name = "static"
	defaultType = "text/html"

	def __init__(self, request, service):
		grend.ServiceBasedPage.__init__(self, request, service)
		try:
			self.indexFile = os.path.join(service.rd.resdir,
				service.getProperty("indexFile"))
		except base.NotFoundError:
			self.indexFile = None
		try:
			self.staticPath = os.path.join(service.rd.resdir,
				service.getProperty("staticData"))
		except base.NotFoundError:
			self.staticPath = None

	@classmethod
	def isBrowseable(self, service):
		return service.getProperty("indexFile", None)

	@classmethod
	def isCacheable(cls, segments, request):
		return False

	def render(self, request):
		if not request.uri.endswith(b"/"):
			# only render these as directories
			raise svcs.WebRedirect(request.uri+b"/")

		if self.indexFile:
			try:
				return ifpages.StaticFile(self.indexFile, self.rd).render(request)
			except os.error as ex:
				raise base.NotFoundError(self.indexFile, "File", "file system",
					hint=str(ex))
		else:
			if self.staticPath is None:
				raise svcs.ForbiddenURI("No static data on this service")

			return ifpages.DirectoryLister(self.staticPath).render(request)

	def getChild(self, name, request):
		segments = request.popSegments(name)
		if segments==[""]:
			return self

		if self.staticPath is None:
			raise svcs.ForbiddenURI("No static data on this service")

		relPath = "/".join(segments)
		destName = os.path.join(self.staticPath, relPath)

		# make sure destName actually is below staticPath so stupid
		# tricks with .. or similar don't work on us
		if not os.path.abspath(destName).startswith(
				os.path.abspath(self.staticPath)):
			raise svcs.ForbiddenURI("%s is not located below staticData"%
				destName)

		if os.path.isdir(destName):
			if not destName.endswith("/"):
				raise svcs.WebRedirect(request.uri+b"/")
			return static.File(destName).directoryListing()

		elif os.path.isfile(destName):
			return ifpages.StaticFile(destName, self.rd,
				defaultType=self.defaultType)

		else:
			raise svcs.UnknownURI("No %s available here."%relPath)


class HiPSRenderer(StaticRenderer):
	"""A static renderer with a few amenities for HiPS trees.

	To make this work, set the service's staticData property
	"""
	name = "hips"
	# for the benefit of properties
	defaultType = "text/plain"
	urlUse = "base"

	def __init__(self, request, service):
		super().__init__(request, service)
		self.indexFile = os.path.join(self.staticPath, "index.html")

	@classmethod
	def isCacheable(cls, segments, request):
		return False

	@classmethod
	def isBrowseable(self, service):
		return True


class FixedPageRenderer(
		grend.CustomTemplateMixin,
		grend.ServiceBasedPage,
		grend.HTMLResultRenderMixin):
	"""A renderer that renders a single template.

	Use something like ``<template key="fixed">res/ft.html</template>``
	in the enclosing service to tell the fixed renderer where to get
	this template from.

	In the template, you can fetch parameters from the URL using
	something like ``<n:invisible n:data="parameter FOO" n:render="string"/>``;
	you can also define new render and data functions on the
	service using customRF and customDF.

	This is, in particular, used for the data center's root page.

	The fixed renderer is intended for non- or slowly changing content.
	It is annotated as cacheable, which means that DaCHS will in general
	only render it once and then cache it.  If the render functions
	change independently of the RD, use the volatile renderer.

	During development, users must add ?nocache=True to a fixed page
	URI to force DaCHS to reload the template.

	Built-in services for such browser apps should go through the //run
	RD.
	"""
	name = "fixed"

	def __init__(self, request, service):
		grend.ServiceBasedPage.__init__(self, request, service)
		self.table = None
		self.customTemplate = None
		try:
			self.customTemplate = self.service.getTemplate(
				self.name, "nocache" in request.strargs)
		except KeyError:
			raise base.ui.logOldExc(
				svcs.UnknownURI("%s renderer needs a '%s' template"%(
					self.name, self.name)))

	def _formatOutput(self, result, request):
		self.table = result and result.getPrimaryTable()
		grend.ServiceBasedPage.render(self, request)

	def render(self, request):
		self.runAsync(request.strargs
			).addCallback(
				self._formatOutput, request
			).addErrback(svcs.renderDCErrorPage, request)
		return server.NOT_DONE_YET

	def data_result(self, request, tag):
		return self.table

	def data_parameter(self, parName):
		"""lets you insert an URL parameter into the template.

		Non-existing parameters are returned as an empty string.
		"""
		def getParameter(request, tag):
			val = request.strargs.get(parName, [""])[0]
			if isinstance(val, bytes):
				val = val.decode("utf-8", "ignore")
			else:
				val = str(val)
			return val
		return getParameter

	@classmethod
	def isCacheable(cls, segments, request):
		return True
	
	@classmethod
	def isBrowseable(self, service):
		return True


class VolatilePageRenderer(FixedPageRenderer):
	"""A renderer rendering a single template with fast-changing results.

	This is like the fixed renderer, except that the results are not cached.
	"""
	name = "volatile"

	@classmethod
	def isCacheable(cls, segments, request):
		return False
