# -*- coding: utf-8 -*-
# ToMaTo (Topology management software) 
# Copyright (C) 2010 Dennis Schwerdel, University of Kaiserslautern
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>
 
from ..auth import permissions
 
def _getTopology(id_):
	id_ = int(id_)
	top = topology.get(id_)
	fault.check(top, "Topology with id #%d does not exist", id_)
	return top
 
def topology_create():
	"""
	Creates an empty topology.
 
	Return value:
	  The return value of this method is the info dict of the new topology as
	  returned by :py:func:`topology_info`. This info dict also contains the
	  topology id that is needed for further manipulation of that object.
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	return topology.create().info()
 
def topology_permissions():
	return permissions.role_descriptions()
 
def topology_remove(id): #@ReservedAssignment
	"""
	Removes and empty topology.
 
	Return value:
	  The return value of this method is ``None``.
 
	Exceptions:
	  The topology must not contain elements or connections, otherwise the call
	  will fail.
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	top.remove()
 
def topology_modify(id, attrs): #@ReservedAssignment
	"""
	Modifies a topology, configuring it with the given attributes.
 
	Currently the only supported attribute for topologies is ``name``.
 
	Additional to the attributes that are supported by the topology,
	all attributes beginning with an underscore (``_``) will be accepted.
	This can be used to store addition information needed by a frontend.
 
	Parameter *id*:
	  The parameter *id* identifies the topology by giving its unique id.
 
	Parameter *attrs*:
	  The attributes of the topology can be given as the parameter *attrs*. 
	  This parameter must be a dict of attributes.
 
	Return value:
	  The return value of this method is the info dict of the topology as 
	  returned by :py:func:`topology_info`. This info dict will reflect all
	  attribute changes.	
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	top.modify(attrs)
	return top.info()
 
def topology_action(id, action, params={}): #@ReservedAssignment
	"""
	Performs an action on the whole topology (i.e. on all elements) in a smart
	way.
 
	The following actions are currently supported by topologies:
 
	  ``prepare``
		This action will execute the action ``prepare`` on all elements in the 
		state ``created``.
 
	  ``destroy``
		This action will first execute the action ``stop`` on all elements in 
		the state ``started`` and then the action ``destroy`` on all elements 
		in the state ``prepared``.
		Note that the states of the elements will be re-evaluated after the 
		first round of actions.
 
	  ``start``
		This action will first execute the action ``prepare`` on all elements
		in the state ``created`` and then the action ``start`` on all elements
		in the state ``prepared``.
		Note that the states of the elements will be re-evaluated after the 
		first round of actions.
 
	  ``stop``
		This action will execute the action ``stop`` on all elements in the 
		state ``started``.
 
	Parameter *id*:
	  The parameter *id* identifies the topology by giving its unique id.
 
	Parameter *action*:
	  The parameter *action* is the action to execute on the topology.
 
	Parameter *params*:
	  The parameters for the action (if needed) can be given as the parameter
	  *params*. This parameter must be a dict if given.
 
	Return value:
	  The return value of this method is  **not the info dict of the 
	  topology**. Instead this method returns the result of the action. Changes
	  of the action to the topology can be checked using 
	  :py:func:`~topology_info`.	
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	return top.action(action, params)
 
def topology_info(id, full=False): #@ReservedAssignment
	"""
	Retrieves information about a topology.
 
	Parameter *id*:
	  The parameter *id* identifies the topology by giving its unique id.
 
	Parameter *full*:
	  If this parameter is ``True``, the fields ``elements`` and 
	  ``connections`` will be a list holding all information of 
	  :py:func:`~backend.tomato.api.elements.element_info`
	  and :py:func:`~backend.tomato.api.connections.connection_info`
	  for each component.
	  Otherwise these fields will be lists holding only the ids of the
	  respective objects.
 
	Return value:
	  The return value of this method is a dict containing information
	  about this topology:
 
	``id``
	  The unique id of the topology.
 
	``elements``
	  A list with all elements. Depending on the parameter *full* this list
	  includes the full information of the elements as given by 
	  :py:func:`~backend.tomato.api.element.element_info` or only the id of the
	  element.
 
	``connections``
	  A list with all connections. Depending on the parameter *full* this list
	  includes the full information of the connections as given by 
	  :py:func:`~backend.tomato.api.connection.connection_info` or only the id
	  of the connection.
 
	``attrs``
	  A dict of attributes of this topology. If this topology does not have
	  attributes, this field is ``{}``.	
 
	``usage``
	  The latest usage record of the type ``5minutes``. See 
	  :doc:`/docs/accountingdata` for the contents of the field.
 
	``permissions``
	  A dict with usernames as the keys and permission levels as values.
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	return top.info(full)
 
def topology_list(full=False, showAll=False, organization=None): #@ReservedAssignment
	"""
	Retrieves information about all topologies the user can access.
 
	Parameter *full*:
	  See :py:func:`~topology_info` for this parameter.
 
	Return value:
	  A list with information entries of all topologies. Each list entry
	  contains exactly the same information as returned by 
	  :py:func:`topology_info`. If no topologies exist, the list is empty. 
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	if organization:
		organization = _getOrganization(organization)
		tops = topology.getAll(permissions__entries__user__organization=organization, permissions__entries__role="owner")		
	elif showAll:
		tops = topology.getAll()
	else:
		tops = topology.getAll(permissions__entries__user=currentUser())
	return [top.info(full) for top in filter(lambda t:t.hasRole("user"), tops)]
 
def topology_permission(id, user, role): #@ReservedAssignment
	"""
	Grants/changes permissions for a user on a topology. See :doc:`permissions`
	for further information about available roles and their meanings.
 
	Parameter *id*:
	  The parameter *id* identifies the topology by giving its unique id.
 
	Parameter *user*:
	  The name of the user.
 
	Parameter *role*:
	  The name of the role for this user. If the user already has a role,
	  if will be changed.
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	user = _getAccount(user)
	top.setRole(user, role)
 
def topology_usage(id): #@ReservedAssignment
	"""
	Retrieves aggregated usage statistics for a topology.
 
	Parameter *id*:
	  The parameter *id* identifies the topology by giving its unique id.
 
	Return value:
	  Usage statistics for the given topology according to 
	  :doc:`/docs/accountingdata`.
	"""
	if not currentUser():
		raise ErrorUnauthorized()
	top = _getTopology(id)
	return top.totalUsage.info()	
 
def topology_import(data):
	if not currentUser():
		raise ErrorUnauthorized()
	fault.check('file_information' in data, "Data lacks field file_information")
	info = data['file_information']
	fault.check('version' in info, "File information lacks field version")
	version = info['version']
 
	if version == 3:
		fault.check('topology' in data, "Data lacks field topology")
		return topology_import_v3(data["topology"])
	else:
		fault.raise_("Unsuported topology version %s" % version, fault.USER_ERROR)
 
def topology_import_v3(top):
	if not currentUser():
		raise ErrorUnauthorized()
	top_id = None
	elementIds = {}   
	connectionIds = {}
	errors = [] 
	try:
		top_id = topology_create()['id']
		try:
			topology_modify(top_id, top['attrs'])
		except:
			for key, value in top['attrs'].iteritems():
				try:
					topology_modify(top_id, {key: value})
				except Exception, ex:
					errors.append(("topology", None, key, value, str(ex)))
		elements = top["elements"]
		elements.sort(key=lambda el: el['id'])
		for el in elements:
			parentId = elementIds.get(el.get('parent'))
			elId = element_create(top_id, el['type'], parent=parentId)['id']
			elementIds[el['id']] = elId
			try:
				element_modify(elId, el['attrs'])
			except:
				for key, value in el['attrs'].iteritems():
					try:
						element_modify(elId, {key: value})
					except Exception, ex:
						errors.append(("element", el['id'], key, value, str(ex)))
		for con in top["connections"]:
			el1 = elementIds.get(con["elements"][0])
			el2 = elementIds.get(con["elements"][1])
			conId = connection_create(el1, el2)['id']
			connectionIds[con['id']] = conId
			try:
				connection_modify(conId, con['attrs'])
			except:
				for key, value in con['attrs'].iteritems():
					try:
						connection_modify(conId, {key: value})
					except Exception, ex:
						errors.append(("connection", con['id'], key, value, str(ex)))
	except:
		topology_remove(top_id)
		raise
	return (top_id, elementIds.items(), connectionIds.items(), errors)
 
 
def topology_export(id): #@ReservedAssignment
	def reduceData(data):
		def reduceData_rec(data, blacklist):
			if isinstance(data, list):
				return [reduceData_rec(el, blacklist) for el in data]
			if isinstance(data, dict):
				return dict(filter(lambda (k, v): k not in blacklist, [(k, reduceData_rec(v, blacklist)) for k, v in data.iteritems()]))
			return data
 
		del data['id']
		del data['permissions']
 
		blacklist = ['usage', 'debug', 'bridge', 'capture_port', 'websocket_pid', 'vmid', 'vncpid',
					 'host', 'websocket_port', 'vncport', 'peers', 'pubkey', 'path', 'port', 
					 'host_fileserver_port', 'capture_pid', 'topology', 'state', 'vncpassword', 
					 'host_info', 'custom_template', 'timeout',
					 'ipspy_pid', 'last_sync',
					 'rextfv_supported', 'rextfv_status', 'rextfv_max_size',
					 'diskspace', 'ram', 'cpus']
		blacklist_elements = ['children', 'connection']
		blacklist_connections = []
		blacklist_attrs = ['_initialized']
		data = reduceData_rec(data, blacklist)
		data['elements'] = reduceData_rec(data['elements'],blacklist_elements)
		data['connections'] = reduceData_rec(data['connections'],blacklist_connections)
		data['attrs'] = reduceData_rec(data['attrs'], blacklist_attrs)
		return data
 
	if not currentUser():
		raise ErrorUnauthorized()
	top_full = topology_info(id, True)
	top = reduceData(top_full)
	return {'file_information': {'version': 3}, 'topology': top}
 
from host import _getOrganization
from account import _getAccount
from .. import fault, topology, currentUser
from elements import element_create, element_modify 
from connections import connection_create, connection_modify
from ..lib.rpc import ErrorUnauthorized  #@UnresolvedImport