"""
SOAP rendering and related classes.
"""

#c Copyright 2008-2021, 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.


from twisted.web import soap

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


class SOAPProcessor(soap.SOAPPublisher):
	"""A helper to the SOAP renderer.

	It's actually a nevow resource ("page"), so whatever really has
	to do with SOAP (as opposed to returning WSDL) is done by this.
	"""
	def __init__(self, request, service, runAsyncFromArgs):
		self.request, self.service = request, service
		self.runAsyncFromArgs = runAsyncFromArgs
		soap.SOAPPublisher.__init__(self)

	def _gotResult(self, result, request, methodName):
# We want SOAP docs that actually match what we advertize in the WSDL.
# So, I override SOAPPublisher's haphazard SOAPpy-based formatter.
		if result is None:  # Error has occurred.  This callback shouldn't be
			# called at all, but for some reason it is, and I can't be bothered
			# now to figure out why.
			return ""
		response = wsdl.serializePrimaryTable(result, self.service)
		self._sendResponse(request, response)
	
	def _gotError(self, failure, request, methodName):
		failure.printTraceback()
		try:
			self._sendResponse(request, 
				wsdl.formatFault(failure.value, self.service), status=500)
		except:
			base.ui.logError("Error while writing SOAP error:")

	def soap_useService(self, *args):
		try:
			return self.runAsyncFromArgs(self.request, args)
		except Exception as exc:
			base.ui.notifyError("SOAP service failed.")
			return self._writeError(exc)
			return ""

	def _writeError(self, exc, request):
		self._sendResponse(self.request, wsdl.formatFault(exc, self.service), 
			status=500)


class SOAPRenderer(grend.ServiceBasedPage):
	"""A renderer that receives and formats SOAP messages.

	This is for remote procedure calls.  In particular, the renderer takes
	care that you can obtain a WSDL definition of the service by
	appending ?wsdl to the access URL.
	"""
	name="soap"
	preferredMethod = b"POST"
	urlUse = "full"

	@classmethod
	def makeAccessURL(cls, baseURL):
		return baseURL+"/soap/go"
	
	def runAsyncFromArgs(self, request, args):
		"""starts the service.

		This being called back from the SOAPProcessor, and args is the
		argument tuple as given from SOAP.
		"""
		inputPars = dict(list(zip(
			[f.name for f in self.service.getInputKeysFor(self)],
			args)))
		return self.runAsyncWithFormalData(inputPars, request)

	def render(self, request):
		"""returns the WSDL for service.

		This is only called when there's a ?wsdl arg in the request,
		otherwise getChild will return the SOAPProcessor.
		"""
		if not hasattr(self.service, "_generatedWSDL"):
			queryMeta = svcs.QueryMeta.fromRequest(request)
			self.service._generatedWSDL = wsdl.makeSOAPWSDLForService(
				self.service, queryMeta).render()
		request.setHeader("content-type", "text/xml")
		return self.service._generatedWSDL

	def getChild(self, name, request):
		if request.uri.endswith(b"?wsdl"): # XXX TODO: use parsed headers here
			return self
		if request.method!=b'POST': 
			# SOAP only makes sense when data is posted; with no data,
			# twisted generates ugly tracebacks, and we may as well do
			# something sensible, like... redirect to the service's info
			# page
			raise svcs.WebRedirect(self.service.getURL("info"))
		return SOAPProcessor(request, self.service, self.runAsyncFromArgs)
