"""
A custom core to run OAI-PMH queries against the records in purx.sources.

This code was originally forked from from rr/res/oaicore.py, but
the use case is different enough that there's little commonality left.

It would probably be smart to structure rr.oairecs like purx.sources and
merge this back.

The oaiinter structure here simply is the purx.sources record, amended
by a key "tree" containing an lxml etree of xml_source.
"""

import re

from lxml import etree

from gavo import base
from gavo import registry
from gavo import rsc
from gavo import utils
from gavo.protocols.oaiclient import getCanonicalPrefixes
from gavo.registry import builders
from gavo.registry import oaiinter
from gavo.registry.model import OAI
from gavo.utils import stanxml


RD_ID = "purx/q"

class RawXML(stanxml.Stub):
	"""A stanxml element spewing out raw stuff.

	No escaping is taking place, and no validation either; also, you're
	completely on your own concerning namespaces.

	So, use this only if you're very certain that what you insert
	makes sense.
	"""
	def __init__(self, rawText):
		self.rawText = rawText

	def __repr__(self):
		return "<RawXML %s>"%self.rawText[:30]

	def write(self, outFile):
		if self.rawText is None:
			# no metadata available (deleted record, hopefully)
			pass
		elif isinstance(self.rawText, str):
			outFile.write(self.rawText.encode("utf-8"))
		else:
			outFile.write(self.rawText)

	def apply(self, func):
		# No children, no namespaces, nothing func could be applied to
		return
	
	def shouldBeSkipped(self):
		return not self.rawText


def ensureMetadataPrefix(pars):
	"""raises errors if the metadata prefix is missing or unsupported.

	This must only be called on functions that actually require a metadata
	prefix.  We need this here because we shortcut execution on some
	functions so the registry.oaiinter validation doesn't kick in.
	"""
	if "metadataPrefix" in pars:
		getSerializerFor(pars["metadataPrefix"])
	else:
		raise oaiinter.BadArgument("metadataPrefix missing")


def makeOAIDCRecord(rec):
	"""returns stanxml for oai_dc metadata for a purx record with tree.
	"""
	if rec["status"]=="DELETED":
		# special handling since we don't want to depend on existing
		# content for deleted records.
		return OAI.record[
				OAI.header(status="deleted")[
				OAI.identifier[rec["ivoid"]],
				OAI.datestamp[utils.formatISODT(rec["rectimestamp"])],
				OAI.setSpec["ivo_managed"]]]

	# there's a tad duplication with _getHeaderFromRec because the
	# latter doesn't use registry.builders code paths.  Ah well.
	mc = base.MetaMixin()
	mc.setMeta("sets", "ivo_managed")
	vorTree = rec["tree"]

	for xpath, metaKey in [
			("title", "title"),
			("identifier", "identifier"),
			("curation/creator/name", "creator.name"),
			("curation/contributor/name", "contributor.name"),
			("content/description", "description"),
			("curation/contact/name", "contact.name"),
			("curation/publisher", "publisher"),]:
		for node in vorTree.xpath(xpath):
			mc.addMeta(metaKey, node.text)
			# hack: the .names all suffer from the issue discussed in
			# http://docs.g-vo.org/DaCHS/ref.html#a-pitfall-with-sequential-nested-meta
			if metaKey.endswith(".name"):
				mc.addMeta(metaKey[:-5], None)

	mc.setMeta("_metadataUpdated", utils.formatISODT(vorTree.get("updated")))
	return builders.getDCResourceElement(mc)


def makeIVOVORRecord(rec):
	"""returns stanxml for oai_dc metadata for a purx record with tree.
	"""
	return OAI.record[
			_getHeaderFromRec(rec),
			OAI.metadata[
				RawXML(rec["xml_source"])]]


def _getHeaderFromRec(rec):
	"""returns stanxml for an OAI-PMH header for a purx.sources record rec.
	"""
	vorTree = rec["tree"]
	mc = base.MetaMixin()
	mc.setMeta("identifier", rec["ivoid"])
	mc.setMeta("_metadataUpdated", utils.formatISODT(rec["rectimestamp"]))
	mc.setMeta("sets", "ivo_managed")
	res = OAI.header[
			builders._oaiHeaderBuilder.build(mc)]
	if rec["status"]=="DELETED":
		res(status="deleted")
	return res


def getSerializerFor(prefix):
	if prefix=="oai_dc":
		return makeOAIDCRecord
	elif prefix=="ivo_vor":
		return makeIVOVORRecord
	else:
		raise registry.CannotDisseminateFormat(
			"%s metadata are not supported"%prefix)


def getListIdentifiersElement(records, prefix):
	return OAI.ListIdentifiers[[
		_getHeaderFromRec(r)
		for r in records]]


def getListRecordsElement(records, prefix):
	s = getSerializerFor(prefix)
	res = OAI.ListRecords()
	for r in records:
		try:
			res[s(r)]
		except Exception as ex:
			base.ui.notifyError("purx cannot serialise record %r: %r"%(
				r, ex))
	return res


def getGetRecordElement(record, prefix):
	return OAI.GetRecord[
		getSerializerFor(prefix)(record)]


def getListSetsElement():
	return OAI.ListSets[
		OAI.set[
			OAI.setSpec["ivo_managed"],
			OAI.setName["ivo_managed"]]]


def _getSetCondition(pars, sqlPars):
# All of our records are ivo_managed and (for now) nothing else.
# So, if any non-ivo_managed set is given, we return empty, else it's
# not a constraint;  however, we may have pending records.  We abuse
# this to select them.
	if "set" in pars and "ivo_managed" not in pars["set"]:
		return "1=2"
	else:
		return "status!='PENDING'"


def getMatchingRecs(pars):
	"""returns a list of identifiers matching pars, plus the metadata prefix.
	"""
	ensureMetadataPrefix(pars)
	td = base.caches.getRD(RD_ID).getById("sources")
	res = []
	for row in oaiinter.getMatchingRows(pars, td, _getSetCondition):
		try:
			if row["xml_source"]:
				row["tree"] = etree.XML(row["xml_source"])
			else:
				# a deleted record
				row["tree"] = None
		except Exception as msg:
			# don't die just because of one record that might have gone bad.
			base.ui.notifyError("Bad purx record: %s"%row["ivoid"])
		res.append(row)
	return (res, pars["metadataPrefix"])


def getRecordForIdentifier(pars):
	"""returns purx.sources records for the OAI-PMH parameters pars.
	"""
	# the replace below is a hack to fix clients that fail to quote
	# plusses; blanks are not allowed in IVORNs anyway, so it can't
	# actually break anything
	identifier = pars["identifier"].lower().replace(" ", "+")

	with base.getTableConn() as conn:
		res = list(conn.queryToDicts(
			"select * from purx.sources where ivoid=%(ivoid)s",
			{"ivoid": identifier}))
		if len(res)==1:
			# the .get for the metadata prefix is so you can use this
			# function to check for the presence of a record.
			res[0]["tree"] = etree.XML(res[0]["xml_source"])
			return res[0], pars.get("metadataPrefix")
	raise oaiinter.IdDoesNotExist(pars["identifier"])


def _makeArgsForListMetadataFormats(pars):
	# returns arguments for builders.getListMetadataElements.
	# identifier is not ignored since crooks may be trying to verify the
	# existence of resource in this way and we want to let them do this.
	# Of course, we support both kinds of metadata on all records.
	if "identifier" in pars:
		getRecordForIdentifier(pars)
	return ()


class Core(oaiinter.RegistryCore):
	builders = {
		"GetRecord": ({"ivo_vor": getGetRecordElement,
			"oai_dc": getGetRecordElement},
			getRecordForIdentifier),
		"ListRecords": ({"ivo_vor": getListRecordsElement,
			"oai_dc": getListRecordsElement},
			getMatchingRecs),
		"ListIdentifiers": ({"ivo_vor": getListIdentifiersElement,
			"oai_dc": getListIdentifiersElement},
			getMatchingRecs),
		"ListSets": (getListSetsElement, lambda pars: ()),
		"Identify": (builders.getIdentifyElement,
			lambda pars: (base.caches.getRD("purx/q").getById("pmh"),)),
		"ListMetadataFormats": (builders.getListMetadataFormatsElement,
			_makeArgsForListMetadataFormats),
	}
