class Transaction: pass

from InformationObject import InformationObject

class IndexContext:

	__database = None
	__connection = None
	__catalog = None
	__catalog_connection = None
	
	def __init__(self,index):
		# Contract
		assert isinstance(index,Index)
		self.__index = index
		self.__database = index.get_database()
		self.__connection = self.__database.open_connection()
		self.__catalog = index.get_catalog()
	
	def __del__(self):
# 		self.__database.abort_transaction(self.__connection)
		if self.__connection: self.__database.close_connection(self.__connection)
		if self.__catalog_connection: self.__catalog_connection.close()
		self.__connection = None
		self.__database = None
		self.__index = None
		self.__catalog_connection = None
	
	def release(self):
		# while this may be automatically garbage-collected, it's better to release it manually once it's no longer needed
		self.abort_transaction() # releasing a context implicitly discards all changes in the objects connected to the context
		if self.__catalog_connection: self.__catalog_connection.abort_transaction()
		self.__del__()
		
	def root(self):
		return self.__database.root(self.__connection)
	
	def set_catalog_infospace(self,name):
		# FIXME should actually call the catalog daemon instead of specifying the db name here
		if self.__catalog_connection: self.__catalog_connection.close()
		self.__catalog_connection = self.__catalog.get_catalog_infospace(name).conn()

	def catalog_object(self,object_key,object):
		if self.__catalog_connection: self.__catalog_connection.catalogObject(object_key,object)
		else: raise LookupError,"Cannot catalog an object without knowing the catalog infospace first"

	def uncatalog_object(self,object_key):
		if self.__catalog_connection: self.__catalog_connection.uncatalogObject(object_key)
		else: raise LookupError,"Cannot uncatalog an object without knowing the catalog infospace first"

	def search(self,expression):
		if self.__catalog_connection: return self.__catalog_connection.search(expression)
		else: raise LookupError,"Cannot search without knowing the catalog infospace first"

	def begin_transaction(self):
		"""Requests that a transaction be started on the connection's context."""
		self.__database.begin_transaction(self.__connection)
		if self.__catalog_connection: self.__catalog_connection.begin_transaction()

# 		self.transaction = Transaction()

	def abort_transaction(self):
		"""Aborts the transaction in the context"""
		self.__database.abort_transaction(self.__connection)
		if self.__catalog_connection: self.__catalog_connection.abort_transaction()
# 		del(self.transaction)

	def commit_transaction(self,message=None):
		"""Commits the transaction in the context"""
		self.__database.commit_transaction(self.__connection)
		if self.__catalog_connection: self.__catalog_connection.commit_transaction()
# 		del(self.transaction)



from common import MetadataServiceException
class IndexException(MetadataServiceException): pass
class TransactionRequiredException(IndexException): pass


from Database import Database
from Component import Component
from pgtools.Catalog import PGCatalog

class Index(Component):

	__database = None
	__catalog = None
	infospace_agents = {}
	
	def __init__(self,database,catalog):
		# contract
		assert isinstance(database,Database)
		assert isinstance(catalog,PGCatalog)
		
		# implementation
		Component.__init__(self,public_interface=IndexInterface(component=self,name="index"))
		self.set_component_name("Index")
		self.__database = database
		self.__database.update_schema()
		self.__catalog = catalog
		self.__catalog.update_schema()

	def register_infospace_agent(self,infospace_agent):
		from InfospaceAgent import InfospaceAgent
		assert isinstance(infospace_agent,InfospaceAgent)
		self.infospace_agents[infospace_agent.infospace_name] = infospace_agent
	
	def acquire_context(self):
		"""Acquire a context to operate with the database. Contexts are automatically released when their reference count drops to zero, so don't forget to unreference them, or explicitly release them with their release() method."""
		return IndexContext(index=self)
	
	def get_database(self): return self.__database
	
	def get_catalog(self): return self.__catalog

	def close(self):
		self.__database.close()
		self.__catalog.close()
		
	def pack(self):
		self.__database.pack()
		self.__catalog.pack()


from PublicInterface import PublicInterface
from pgtools.query import normalize_query
from common import transcode
import locale
import logging
import time

class IndexInterface(PublicInterface):
	""" An abstraction encapsulating possible user-level requests to the search engine."""
	
	def pack(self):
		t = time.time()
		index = self.get_component()
		logging.getLogger("IndexInterface").debug("packing database")
		index.pack()
		logging.getLogger("IndexInterface").debug("packed database in %s sec",time.time()-t)
		return True
		

	def search(self,query):
		logging.getLogger("IndexInterface").debug("searching with query %s",query)
		""" Delegate search to infospace agents, collate results and return them """
		# this should: 1) determine which infospaces are alluded, 2) compose a Query object for each of the alluded infospaces, 3) submit the query to each infospace, 4) aggregate each infospace's result in a single result set, 5) and submit the result set back to the client
		import time
		thetime = time.time()
		context = None
		try:
			query = normalize_query(query) # normalize_Query or some other function NEEDS to transform UTF-8 encoded data and dates to the internal representations (platform-encoded strings and whatever may be the flavor of the dates' timezones I choose this month).
			
			index = self.get_component()
			context = index.acquire_context()
			
			# compute the involved infospace list
			# and prune the infospace list from those that don't support searches
			if len(query.infospaces) == 0: infospaces = index.infospace_agents.values()
			else:
				infospaces = []
				for iname in query.infospaces:
					try:
						if hasattr(index.infospace_agents[iname],"search"):
							infospaces.append(index.infospace_agents[iname])
					except KeyError: raise LookupError, "Unknown infospace %s"%iname
			
			objects = []
			
			for i in infospaces:
				results = None
				# infospace must return a list of URLs, pre-encoded in UTF-8
				results = i.search(expression = query.expression, context = context)
				if results is not None:
					assert type(results) is list
					objects.extend(results)
			
			results = objects
			
			logging.getLogger("IndexInterface").debug("Search returned %s results after %s sec. (raw query time)", len(results), time.time() - thetime)
			thetime = time.time()
# 			results = [ object.as_marshallable_representation() for object in objects ]
# 			logging.getLogger("IndexInterface").debug("Search marshalled objects after %s sec.", time.time() - thetime)
			context.release()
		
		except:
			if context: context.release()
			logging.getLogger("IndexInterface").error("Error on search",exc_info=True)
			raise
		
		return results
