"""
A core getting one of two upload sources and crossmatching an ADQL-accessible
table with it.
"""

import sys

from gavo import base
from gavo import rsc
from gavo import svcs
from gavo import utils
from gavo.formats import votable
from gavo.svcs import core
#from gavo.utils import pgexplain
from gavo.svcs.standardcores import mapDBErrors


class InventingQuotedNameMaker(votable.QuotedNameMaker):
	"""A QuotedNameMaker that will make up new names for illegal quoted names.
	"""
	def makeName(self, field):
		try:
			return votable.QuotedNameMaker.makeName(self, field)
		except base.ValidationError:
			stem, dis = "Field%02d"%self.index, ""
			while True:
				if stem+dis not in self.seenNames:
					return stem+dis
				dis = dis+"_"


class Core(core.Core):
	inputTableXML = """<inputTable>
		<inputKey name="fileSrc" type="file" tablehead="Local file"
			description="A local file to upload (overrides remote table if given)."/>
		<inputKey name="urlSrc" type="text" tablehead="Remote URL"
			description="A URL for a table to crossmatch."/>
		<inputKey name="tableName" type="text" tablehead="Target Table"
			required="True"
			description="Name of the table to match against."
			multiplicity="single">
			<values fromdb="tablename from dc.tablemeta where adql=True"/>
		</inputKey>
		<inputKey name="SR" type="real" tablehead="Search radius"
			unit="deg" required="True" description="Search radius in crossmatch">
			<values default="0.001"/>
		</inputKey>
		</inputTable>
		"""
	# we don't know what's going to be in the output table until we
	# know both the uploaded table and the tableName
	outputTableXML = """<outputTable/>"""

	uploadName = "crossupload"
	
	def wantsTableWidget(self):
		return True

	def _ingestUpload(self, inF, uploadSrc, connection, gunzip=False):
		try:
			t = votable.uploadVOTable(self.uploadName, inF, connection,
				gunzip=gunzip, mixin="q3cindex",
				nameMaker=InventingQuotedNameMaker())
		except Exception as msg:
			raise base.ValidationError("Error while importing the VOTable --"
				" is your input a valid VOTable?  (Message: %s)"%msg,
				colName=uploadSrc)
		if t is None:
			raise base.ValidationError("Your VOTable does not contain"
				" a table.", colName=uploadSrc, hint="This usually means the"
				" VOTable contains an error message, i.e., the generating"
				" server could not fulfil your previous query.")
		return t

	def _getPosCols(self, tableDef):
		"""returns a pair of column names for the ra and dec columns in tableDef.

		This is based on UCDs pos.eq.*;meta.main or POS_EQ_*_MAIN.
		"""
		try:
			raCol = tableDef.getColumnByUCDs("pos.eq.ra;meta.main",
				"POS_EQ_RA_MAIN")
			decCol = tableDef.getColumnByUCDs("pos.eq.dec;meta.main",
				"POS_EQ_DEC_MAIN")
		except (base.LiteralParseError, base.StructureError):
			raise base.ValidationError("Cannot find main position in table",
				colName="tableName")
		return "%s.%s, %s.%s"%(tableDef.getQName(), raCol.name,
			tableDef.getQName(), decCol.name)

	def _doQuery(self, uploadTD, targetTD, searchRadius, queryLimit, connection):
		outputTableDef = svcs.OutputTableDef.fromColumns(
			targetTD.columns+[c.change(name="ext_"+c.key) for c in uploadTD.columns])
		query = ("SELECT %s, %s FROM %s JOIN %s"
			" ON q3c_join(%s, %s, %%(searchRadius)s) LIMIT %%(queryLimit)s")%(
			", ".join("%s.%s"%(targetTD.getQName(), c.name) for c in targetTD),
			", ".join("%s.%s"%(self.uploadName, c.name) for c in uploadTD),
			targetTD.getQName(),
			self.uploadName,
			self._getPosCols(uploadTD),
			self._getPosCols(targetTD))
		cursor = connection.cursor()
# The limit in the statement above takes postgres' planner completey whacko
# Remove this when a new version of postgres comes along.
		cursor.execute("SET enable_nestloop=False")
		cursor.execute("SET enable_seqscan=False")
		cursor.execute(query, locals())
		res = rsc.TableForDef(outputTableDef)
		for tup in cursor.fetchall():
			res.addTuple(tup)
		if len(res)==queryLimit:
			res.addMeta("_warning", "The query limit was reached.  Increase it"
				" to retrieve more matches.  Note that unsorted truncated queries"
				" are not reproducible (i.e., might return a different result set"
				" at a later time).")
		return res

	def _ingestFromURL(self, inURL, connection):
		# (mostly for testing: urls without server name)
		if not "://" in inURL:
			inURL = base.getConfig("web", "serverURL")+inURL

		try:
			uploaded = self._ingestUpload(utils.urlopenRemote(inURL),
				'urlSrc', connection)
		except base.Error:
			raise # hand through our errors
		except Exception as msg:  # all others mean there's something wrong
			   # with the url
			raise base.ValidationError("This URL appears to be invalid (%s)."%msg,
				'urlSrc')
		return uploaded

	def _prepareInput(self, inRow, connection):
		inF, inURL, targetTable = None, inRow['urlSrc'], inRow["tableName"]
		searchRadius = inRow["SR"]
		# Check for position columns here while we know where it came from.

		if inRow['fileSrc'] and inRow['fileSrc'][1].getvalue()!=b'': 
			# upload available
			inName, inF = inRow['fileSrc']
			uploaded = self._ingestUpload(inF, 'fileSrc', connection,
				gunzip=inName.endswith(".gz"))
			try:
				self._getPosCols(uploaded.tableDef)
			except base.ValidationError as ex:
				ex.colName = "fileSrc"
				raise
		elif inURL is not None:
			uploaded = self._ingestFromURL(inURL, connection)
			try:
				self._getPosCols(uploaded.tableDef)
			except base.ValidationError as ex:
				ex.colName = "urlSrc"
				raise
		else:
			raise base.ValidationError("You must give one of local file or"
				" remoteURL", 'urlSrc')
		return uploaded, searchRadius, targetTable

	def _doWork(self, inRow, queryLimit, connection):
		uploaded, searchRadius, targetTable = self._prepareInput(inRow, connection)
		with uploaded.connection.parameters([
				("statement_timeout", 200*1000)]):
			uploadTD = uploaded.tableDef
			targetTD = rsc.MetaTableHandler().getTableDefForTable(targetTable)
			try:
				res = self._doQuery(uploadTD, targetTD, searchRadius,
					queryLimit, connection)
			except:
				mapDBErrors(*sys.exc_info())
			uploaded.drop()
			res.noPostprocess = True
			return res

	def run(self, service, inputTable, queryMeta):
		with base.getWritableUntrustedConn() as connection:
			inRow = inputTable.getParamDict()
			return self._doWork(inRow, queryMeta["dbLimit"], connection)
