# -*- coding: utf-8 -*-

def setActionRequestWhereOutdated(self, actionRequest, productId):
	"""
	Set the specified `actionRequest` at every client that has not the
	version of the product with the given `productId` installed.
	The version is compared between the version installed on the client
	and the one available on the depot.

	:return: The IDs of clients where the actionRequest has been set.
	:returntype: set
	"""
	productId = forceProductId(productId)
	actionRequest = forceActionRequest(actionRequest)

	if not self.product_getObjects(id=productId):
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u"No product with id {0!r}".format(productId))

	clientsToUpdate = self.getClientsWithOutdatedProduct(productId)
	for clientId in clientsToUpdate:
		self.setProductActionRequest(productId, clientId, actionRequest)

	return clientsToUpdate

def getClientsWithOutdatedProduct(self, productId):
	"""
	Get clients where the product with id `productId` is outdated.
	This does only take clients into account where the installationStatus
	is not_installed or unknown.

	:return: The IDs of clients with an outdated version installed.
	:returntype: set
	"""
	from collections import defaultdict

	depotToClients = defaultdict(set)
	for clientToDepot in self.configState_getClientToDepotserver():
		depotToClients[clientToDepot['depotId']].add(clientToDepot['clientId'])

	updatedClients = set()
	unwantedStatus = frozenset([u'not_installed', u'none', u'unknown'])
	for depot, clientIds in depotToClients.items():
		if not clientIds:
			continue

		for productOnDepot in self.productOnDepot_getObjects(productId=productId, depotId=depot):
			logger.debug(u"Checking {0!r}...".format(productOnDepot))
			for productOnClient in self.productOnClient_getObjects(clientId=clientIds, productId=productOnDepot.productId):
				if not productOnClient.installationStatus or productOnClient.installationStatus in unwantedStatus:
					logger.debug(u"Skipping {0!r}".format(productOnClient))
					continue

				if (productOnClient.productVersion != productOnDepot.productVersion
					or productOnClient.packageVersion != productOnDepot.packageVersion):

					logger.debug(u"Version difference at {0!r}".format(productOnClient))
					updatedClients.add(productOnClient.clientId)

	return updatedClients

def setActionRequestWhereOutdatedWithDependencies(self, actionRequest, productId):
	"""
	Set the specified `actionRequest` for the given `productId` and
	dependencies at every client that hasn't got the current version
	installed.
	The version is compared between the version installed on the client
	and the one available on the depot.

	:return: The IDs of clients where the actionRequest has been set.
	:returntype: set
	"""
	productId = forceProductId(productId)
	actionRequest = forceActionRequest(actionRequest)

	if not self.product_getObjects(id=productId):
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u"No product with id {0!r}".format(productId))

	clientsToUpdate = self.getClientsWithOutdatedProduct(productId)
	for clientId in clientsToUpdate:
		self.setProductActionRequestWithDependencies(productId, clientId, actionRequest)

	return clientsToUpdate

def setupWhereNotInstalled(self, productId):
	"""
	Sets the action request for the product with `productId` to 'setup'
	on all clients where the status of the product is not 'installed'.

	The action request will only be set if the depot the client is assigend
	to has a product installed where 'setup' can be set.

	:return: the ID of all clients that have been processed.
	:returntype: set
	"""
	from collections import defaultdict

	productId = forceProductId(productId)

	products = frozenset(self.product_getObjects(id=productId))
	if not products:
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u"No product with id {0!r}".format(productId))

	depotToClients = defaultdict(set)
	for clientToDepot in self.configState_getClientToDepotserver():
		depotToClients[clientToDepot['depotId']].add(clientToDepot['clientId'])

	clientsToSetup = set()
	for pod in self.productOnDepot_getObjects(productId=productId):
		for product in products:
			if (product.packageVersion == pod.packageVersion
				and product.productVersion == pod.productVersion
				and product.getSetupScript()):

				try:
					for clientId in depotToClients[pod.depotId]:
						clientsToSetup.add(clientId)
				except KeyError as notfound:
					logger.debug("{0!r} not found: {1!r}".format(pod.depotId, notfound))

	if not clientsToSetup:
		logger.info(u"No clients found where 'setup' is possible.")
		return set()

	logger.debug(u"Clients possible to 'setup': {0}".format(clientsToSetup))
	clientsWithProductInstalled = set(
		poc.clientId for poc in
		self.productOnClient_getObjects(
			['clientId'],
			productId=productId,
			clientId=clientsToSetup,
			installationStatus='installed'
		)
	)
	clientsToSetup.difference_update(clientsWithProductInstalled)

	logger.debug(u"Clients to 'setup': {0}".format(clientsToSetup))
	for clientId in clientsToSetup:
		self.setProductActionRequest(productId, clientId, u'setup')

	return clientsToSetup

def updateWhereInstalled(self, productId):
	"""
	Set the product with the id `productId` to 'update' on every client
	where the installation status is 'installed'.

	The action request will only be set if the depot the client is assigend
	to has a product installed where 'uninstall' can be set.

	:return: the ID of all clients that have been processed.
	:returntype: set
	"""
	from collections import defaultdict

	productId = forceProductId(productId)

	products = self.product_getObjects(id=productId)
	if not products:
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u'No product with id {0!r} found'.format(productId))

	clientsWithProductInstalled = set(
		poc.clientId for poc in self.productOnClient_getObjects(
			productId=productId,
			installationStatus=u'installed'
		)
	)
	if not clientsWithProductInstalled:
		logger.notice('No clients have {0!r} installed.'.format(productId))
		return set()

	depotToClients = defaultdict(set)
	for clientToDepot in self.configState_getClientToDepotserver():
		depotToClients[clientToDepot['depotId']].add(clientToDepot['clientId'])

	clientsToUpdate = set()
	for product in products:
		logger.debug(u"Processing {0!r}".format(product))

		if product.getUpdateScript():
			pods = self.productOnDepot_getObjects(
				productId=product.id,
				productVersion=product.productVersion,
				packageVersion=product.packageVersion
			)

			for depot in (pod.depotId for pod in pods):
				for clientId in (c for c in depotToClients[depot] if c in clientsWithProductInstalled):
					clientsToUpdate.add(clientId)

	logger.debug(u"Clients to 'update': {0}".format(clientsToUpdate))
	for clientId in clientsToUpdate:
		self.setProductActionRequest(productId, clientId, u'update')

	return clientsToUpdate

def uninstallWhereInstalled(self, productId):
	"""
	Set the product with the id `productId` to 'uninstall' on every client
	where the installation status is 'installed'.

	The action request will only be set if the depot the client is assigend
	to has a product installed where 'uninstall' can be set.

	:return: the ID of all clients that have been processed.
	:returntype: set
	"""
	from collections import defaultdict

	productId = forceProductId(productId)

	products = self.product_getObjects(id=productId)
	if not products:
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u"No product with id {0!r}".format(productId))

	clientsWithProductInstalled = set(poc.clientId for poc in self.productOnClient_getObjects(
		productId=productId,
		installationStatus=u'installed'
	))
	if not clientsWithProductInstalled:
		logger.notice('No clients have {0!r} installed.'.format(productId))
		return set()

	depotToClients = defaultdict(set)
	for clientToDepot in self.configState_getClientToDepotserver():
		depotToClients[clientToDepot['depotId']].add(clientToDepot['clientId'])

	clientsToUninstall = set()
	for product in products:
		logger.debug(u"Processing {0!r}...".format(product))
		if product.getUninstallScript():
			pods = self.productOnDepot_getObjects(
				productId=product.id,
				productVersion=product.productVersion,
				packageVersion=product.packageVersion
			)

			for depot in (pod.depotId for pod in pods):
				for clientId in (c for c in depotToClients[depot] if c in clientsWithProductInstalled):
					clientsToUninstall.add(clientId)

	logger.debug(u"Clients to 'uninstall': {0}".format(clientsToUninstall))
	for clientId in clientsToUninstall:
		self.setProductActionRequest(productId, clientId, u'uninstall')

	return clientsToUninstall

def setupWhereInstalled(self, productId):
	"""
	Set the product with the id `productId` to 'setup' on every client
	where the installation status is 'installed'.

	The action request will only be set if the depot the client is assigend
	to has a product installed where 'uninstall' can be set.

	:return: the ID of all clients that have been processed.
	:returntype: set
	"""
	from collections import defaultdict

	productId = forceProductId(productId)

	products = self.product_getObjects(id=productId)
	if not products:
		from OPSI.Types import BackendMissingDataError

		raise BackendMissingDataError(u"No product with id {0!r}".format(productId))

	clientsWithProductInstalled = set(poc.clientId for poc in self.productOnClient_getObjects(
		productId=productId,
		installationStatus=u'installed'
	))
	if not clientsWithProductInstalled:
		logger.notice('No clients have {0!r} installed.'.format(productId))
		return set()

	depotToClients = defaultdict(set)
	for clientToDepot in self.configState_getClientToDepotserver():
		depotToClients[clientToDepot['depotId']].add(clientToDepot['clientId'])

	clientsToSetup = set()
	for product in products:
		logger.debug(u"Processing {0!r}...".format(product))
		if product.getSetupScript():
			pods = self.productOnDepot_getObjects(
				productId=product.id,
				productVersion=product.productVersion,
				packageVersion=product.packageVersion
			)

			for depot in (pod.depotId for pod in pods):
				for clientId in (c for c in depotToClients[depot] if c in clientsWithProductInstalled):
					clientsToSetup.add(clientId)

	logger.debug(u"Clients to 'setup': {0}".format(clientsToSetup))
	for clientId in clientsToSetup:
		self.setProductActionRequest(productId, clientId, u'setup')

	return clientsToSetup
