"""
User-defined cores, either with an external file or with code within
the RD.
"""

#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.


from gavo import base
from gavo import rsc
from gavo import rscdef
from gavo import utils
from gavo.svcs import core


class CustomCore(core.Core):
	"""A wrapper around a core defined in a module.

	This core lets you write your own cores in modules.

	The module must define a class Core.  When the custom core is
	encountered, this class will be instantiated and will be used
	instead of the CustomCore, so your code should probably inherit
	core.Core.

	See `Writing Custom Cores`_ for details.
	"""
	name_ = "customCore"

	_module = rscdef.ResdirRelativeAttribute("module",
		description="Path to the module containing the core definition.",
		copyable=True, default=base.Undefined)
	
	def completeElement(self, ctx):
		overrideInput = self.inputTable is not base.NotGiven
		overrideOutput = self.outputTable is not base.NotGiven

		super().completeElement(ctx)

		try:
			loaded_module, _ = utils.loadPythonModule(self.module)
		except Exception as msg:
			raise base.StructureError("Cannot load custom core %s: %s"%(
				self.module, msg))
		innerCore = base.makeStruct(loaded_module.Core, parent_=self.parent)
		innerCore.id = self.id

		# let people overwrite inputTable and outputTable from the RD
		# (this may be a questionable feature, but there you go.
		if overrideInput:
			innerCore.inputTable = self.inputTable
		if overrideOutput:
			innerCore.outputTable = self.outputTable

		# and we let people override properties on the inner core, too.
		innerCore.properties.update(self.properties)

		# now replace the "empty" core from the xml in the id map and in the
		# parent with our inner core
		if ctx.idmap.get(self.id) is self:
			ctx.idmap[self.id] = innerCore
		self.parent.feedObject(self.name_, innerCore)

	def onElementComplete(self):
		super().onElementComplete()
		# we have already added the inner core in completeElement, so we
		# should not leave any trace here.
		raise base.Ignore(self)
	

class CoreProc(rscdef.ProcApp):
	"""A definition of a pythonCore's functionality.

	This is a procApp complete with setup and code; you could inherit
	between these.

	coreProcs see the embedding service, the input table passed, and the
	query metadata as service, inputTable, and queryMeta, respectively.

	The core itself is available as self.
	"""
	name_ = "coreProc"
	requiredType = "coreProc"
	formalArgs = "self, service, inputTable, queryMeta"

	additionalNamesForProcs = {
		"rsc": rsc
	}


class PythonCore(core.Core):
	"""A core doing computation using a piece of python.

	See `Python Cores instead of Custom Cores`_ in the reference.
	"""
	name_ = "pythonCore"

	_computer = base.StructAttribute("coreProc", default=base.Undefined,
		childFactory=CoreProc,
		description="Code making the outputTable from the inputTable.",
		copyable=True)

	def expand(self, s):
		# macro expansion should ideally take place in the service,
		# but that's impossible in general because a core could be
		# in use by several services.  Hence, we go ask the RD
		return self.rd.expand(s)

	def run(self, service, inputTable, queryMeta):
		return self.coreProc.compile()(self, service, inputTable, queryMeta)
