"""
A manual registry of renderers, plus some helper code to produce HTML.

Renderers are the glue between a core and some output.  A service is the
combination of a subset of renderers and a core.

Renderers are actually defined in web.grend, but we need some way to
get at them from svcs and above, so the registry is kept here.

We also have doctypedStan here and CommonRenderers; that's admittedly
mainly for convenience.
"""

#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 functools
import re
from urllib import parse as urlparse

from twisted.web.template import tags as T

from twisted.web import template

from gavo import base
from gavo import utils

JSEXT = ".js"

RENDERER_REGISTRY = {
	'admin': ("web.adminrender", "AdminRenderer"),
	'api': ("web.vodal", "APIRenderer"),
	'async': ("web.asyncrender", "DALIAsyncRenderer"),
	'availability': ("web.vosi", "VOSIAvailabilityRenderer"),
	'biblinks.json': ("web.vodal","BiblinksRenderer"),
	'capabilities': ("web.vosi", "VOSICapabilityRenderer"),
	'coverage': ("web.metarender", "CoverageRenderer"),
	'custom': ("web.customrender", "CustomRenderer"),
	'dali': ("web.syncrender", "DALIRenderer"),
	'dlget': ("web.vodal", "DatalinkGetDataRenderer"),
	'dlasync': ("web.asyncrender", "DatalinkAsyncRenderer"),
	'dlmeta': ("web.vodal", "DatalinkGetMetaRenderer"),
	'edition': ("web.metarender", "EditionRenderer"),
	'examples': ("web.examplesrender", "Examples"),
	'external': ("web.metarender", "ExternalRenderer"),
	'fixed': ("web.constantrender", "FixedPageRenderer"),
	'form': ("web.formrender", "FormRenderer"),
	'get': ("web.productrender", "ProductRenderer"),
	'hips': ("web.constantrender", "HiPSRenderer"),
	'howtocite': ("web.metarender", "HowToCiteRenderer"),
	'info': ("web.metarender", "ServiceInfoRenderer"),
	'mupload': ("web.uploadservice", "MachineUploader"),
	'pubreg.xml': ("web.vodal", "RegistryRenderer"),
	'qp': ("web.qprenderer", "QPRenderer"),
	'rdinfo': ("web.metarender", "RDInfoRenderer"),
	'scs.xml': ("web.vodal", "SCSRenderer"),
	'siap.xml': ("web.vodal", "SIAPRenderer"),
	'siap2.xml': ("web.vodal", "SIAP2Renderer"),
	'slap.xml': ("web.vodal", "SLAPRenderer"),
	'soap': ("web.soaprender", "SOAPRenderer"),
	'ssap.xml': ("web.vodal", "SSAPRenderer"),
	'static': ("web.constantrender", "StaticRenderer"),
	'sync': ("web.syncrender", "SyncRenderer"),
	'tableinfo': ("web.metarender", "TableInfoRenderer"),
	'tableMetadata': ("web.vosi", "VOSITablesetRenderer"),
	'tables': ("web.vosi", "VOSITablesetRenderer"),
	'tablenote': ("web.metarender", "TableNoteRenderer"),
	'user_tables': ("web.tap_uploads", "PersistentUploadRenderer"),
	'upload': ("web.uploadservice", "Uploader"),
	'uws.xml': ("web.asyncrender", "DALIAsyncRenderer"),
	'voevent': ("web.metarender", "VOEventRenderer"),
	'volatile': ("web.constantrender", "VolatilePageRenderer"),
}


@functools.lru_cache()
def getRenderer(name):
	if name not in RENDERER_REGISTRY:
		raise base.NotFoundError(name, "renderer", "registered renderers")
	cls = utils.loadInternalObject(*RENDERER_REGISTRY[name])

	if cls.name!=name and not name in cls.aliases:
		raise base.ReportableError("Internal Error: Renderer %s is registered"
			" under the wrong name."%name,
			hint="This is probably a typo in svcs.renderers; it needs"
			" to be fixed there")
	return cls


class CommonRenderers(object):
	"""A base for renderer (python) mixins within the DaCHS

	Including standard stylesheets/js/whatever:
	<head n:render="commonhead">...</head>

	Rendering internal links (important for off-root operation):

	* <tag href|src="/foo" n:render="rootlink"/>

	"""
	@template.renderer
	def unicode(self, request, tag):
		"""returns unicode(data); this is deprecated in favour of string.
		"""
		return tag[str(tag.slotData)]

	@template.renderer
	def rootlink(self, request, tag):
		def munge(key):
			if key in tag.attributes:
			 tag.attributes[key] = base.makeSitePath(tag.attributes[key])
		munge("src")
		munge("href")
		return tag

	@template.renderer
	def commonhead(self, request, tag):
		#	we do not want to blindly append these things to the tag since user-
		# provided items in the header should have access to this environment,
		# in particular to jquery; thus, this stuff must be as early as possible.
		originalChildren = tag.children[:]
		tag.clear()
		res = tag[
			T.meta(**{"http-equiv": "Content-type",
				"content": "text/html;charset=UTF-8"}),
			T.link(rel="stylesheet",
				href=base.makeSitePath("/static/css/formal.css"),
				type="text/css"),
			T.link(rel="stylesheet", href=base.makeSitePath(
				"/static/css/gavo_dc.css"), type="text/css"),
			T.script(src=base.makeSitePath("/static/js/jquery-gavo.js"),
				type="text/javascript"),
			T.script(src=base.makeSitePath("/static/js/gavo"+JSEXT),
				type="text/javascript"),
			originalChildren,
		]
		if base.getConfig("web", "operatorCSS"):
			res[
				T.link(rel="stylesheet", type="text/css",
					href=base.getConfig("web", "operatorCSS"))]
		return res

	@template.renderer
	def urlescape(self, request, tag):
		"""renders data as a url-escaped string.
		"""
		data = tag.slotData
		return urlparse.quote(data)
	
	@template.renderer
	def getconfig(self, request, tag):
		"""looks up the text child in the DaCHS configuration and inserts
		the value as a (unicode) string.

		The config key is either [section]item or just item for something
		in [general].  Behaviour for undefined config items is undefined.
		"""
		configKey = tag.children[0].strip()
		mat = re.match(r"\[([^]]*)\](.*)", configKey)
		if mat:
			content = base.getConfig(mat.group(1), mat.group(2))
		else:
			content = base.getConfig(configKey)
		tag.clear()
		return tag[content]


class doctypedStan(template.TagLoader):
	"""is the TagLoader loader that arranges for a an XHTML doctype and
	namespace.

	As this relies on a magic attribute gavo_useDoctype, this will only
	work if the template will be rendered through nevowc.TemplatedPage.
	"""

	DOCTYPE = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
		' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">')

	def __init__(self, rootEl):
		rootEl(xmlns="http://www.w3.org/1999/xhtml")
		rootEl.gavo_useDoctype = self.DOCTYPE
		template.TagLoader.__init__(self, rootEl)
