#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

import sys

from winswitch.util.simple_logger import Logger
logger = Logger("session")
debug_import = logger.get_debug_import()


debug_import("stateful_object")
from winswitch.objects.stateful_object import StatefulObject
debug_import("common")
from winswitch.util.common import parse_screensize
debug_import("consts")
from winswitch.consts import XPRA_TYPE, NX_TYPE, SSH_TYPE, VNC_TYPE, GSTVIDEO_TYPE, X11_TYPE, SCREEN_TYPE, WINDOWS_TYPE, OSX_TYPE, LIBVIRT_TYPE, VIRTUALBOX_TYPE
debug_import("all done")

ALLOW_SSH_DESKTOP_MODE = True		#buggy
ALLOW_RDP_SEAMLESS = "--enable-seamless-rdp" in sys.argv			#just can't seem to use this for anything remotely useful


def do_get_available_session_types(xpra, xpra_desktop, nx, ssh, ssh_desktop, vnc, libvirt, vbox, rdp, rdp_desktop, desktop_mode, hide_suboptimal):
	""" each variable passed in is a boolean telling us if that protocol is supported,
		desktop_mode=True|False|None allows us to select desktop mode (True), seamless mode only (False), or both (None)
	"""
	types = []
	subs = []
	#Xpra:
	if xpra_desktop and desktop_mode is not False:
		types.append(XPRA_TYPE)
	if xpra and desktop_mode is not True:
		types.append(XPRA_TYPE)
	#vnc non-desktop mode is suboptimal
	if vnc:
		if desktop_mode is not True:
			subs.append(VNC_TYPE)			#VNC seamless mode is a hack
		if desktop_mode is not False:
			types.append(VNC_TYPE)
	#NX:
	if nx:
		types.append(NX_TYPE)
	#SSH-X11
	if ssh_desktop and desktop_mode is not False and ALLOW_SSH_DESKTOP_MODE:
		subs.append(SSH_TYPE)			#ssh desktop mode is a hack
	if ssh and desktop_mode is not True:
		types.append(SSH_TYPE)
	#libvirt
	if libvirt:
		types.append(LIBVIRT_TYPE)
	if vbox and desktop_mode is not False:
		types.append(VIRTUALBOX_TYPE)
	#RDP:
	if rdp_desktop and desktop_mode is not False:
		types.append(WINDOWS_TYPE)
	if rdp and desktop_mode is not True and ALLOW_RDP_SEAMLESS:
		subs.append(WINDOWS_TYPE)			#RDP seamless does not even work properly!
	if not hide_suboptimal:
		for x in subs:
			if x not in types:
				types.append(x)
	return	types

def session_status_has_actor(status):
	return	status in [Session.STATUS_IDLE, Session.STATUS_CONNECTED, Session.STATUS_CONNECTING, Session.STATUS_SUSPENDING]



class Session(StatefulObject):
	STATUS_CONNECTED = "connected"			#in use
	STATUS_IDLE = "idle"					#in use but idle (screensaver, ...)
	STATUS_CONNECTING = "connecting"		#waiting for client to attach
	STATUS_AVAILABLE = "available"			#detached
	STATUS_SUSPENDING = "suspending"		#nx is going to suspend the session (transient state)
	STATUS_SUSPENDED = "suspended"			#nx suspended the session
	STATUS_STARTING = "starting"			#starting
	STATUS_CLOSED = "closed"
	STATUS_UNAVAILABLE = "unavailable"		#cant be accessed

	ALL_BUT_CLOSED = [STATUS_CONNECTED, STATUS_IDLE, STATUS_CONNECTING, STATUS_AVAILABLE, STATUS_SUSPENDING, STATUS_SUSPENDED, STATUS_STARTING, STATUS_UNAVAILABLE]
	ALL_STATUS = ALL_BUT_CLOSED+[STATUS_CLOSED]
	FINAL_STATUS = [STATUS_CLOSED]

	SESSION_UPDATE_COMMON_FIELDS = ["name", "host", "port", "user", "owner", "actor",
								"command", "command_uuid", "preload", "start_time",
								"uses_sound_out", "uses_sound_in",
								"options",
								]

	PERSIST_COMMON = [
			"# Identification:",
			"ID", "display", "name", "user", "session_type", "start_time",
			"# Connection point:",
			"host", "port", "password", "encrypted_password",
			"# ownership:",
			"owner", "actor",
			"# command details:",
			"command", "commands", "arguments", "command_uuid",
			"# virtualization settings:",
			"full_desktop", "shared_desktop", "read_only", "shadowed_display",
			"# screen settings:",
			"screen_size",
			"# status:",
			"status", "preload",
			"# sound options:",
			"can_export_sound", "can_import_sound", "can_clone_sound", "uses_sound_out", "uses_sound_in",
			]

	def __init__(self):
		StatefulObject.__init__(self, Session.ALL_STATUS, Session.FINAL_STATUS)
		# From remote:
		self.ID = ""
		self.display = ""
		self.name = ""
		self.host = ""
		self.port = 0
		self.password = ""
		self.encrypted_password = ""
		self.user = ""					# the actual unix user
		self.owner = ""					# the UUID of the user who controls this session
		self.actor = ""					# the UUID of the user currently connected to this session
		self.ipp_port = 0
		self.requires_tunnel = False	# NX sessions may require a tunnel when not bound to the interface the client is connected to
		self.full_desktop = False		# True for sessions showing a full desktop
		self.shared_desktop = False		# Are multiple users allowed to connect at the same time?
		self.read_only = False			# For shared ("shadow" sessions)
		self.shadowed_display = ""		# The display being shadowed
		self.screen_size = ""			# specify the screen size for virtual desktops, ie: 800x600[-32]
		self.command = ""				# the main command, as shown to the user
		self.commands = []				# the list of commands [to be] started by this session
		self.command_uuid = ""
		self.arguments = ""
		self.session_type = ""
		self.options = {}
		self.preload = False
		self.can_export_sound = False	# can the server export sound generated by this session (so client can listen to it)
		self.can_import_sound = False	# can the server import sound into this session (so client can send their sound to it)
		self.can_clone_sound = False	# can the server clone the existing sound from this session (akin to export but using "monitor" channels)
										# this conflicts with export and import as it can create sound loops.
		self.uses_sound_out = False		# this session will use sound output (speaker / export)
		self.uses_sound_in = False		# this session will use sound input (mic / input)
		self.start_time = 0
		self.init_transients()

	def __str__(self):
		return	"Session(%s - %s - %s)" % (self.display, self.session_type, self.status)

	def __hash__(self):
		return self.ID.__hash__()

	def __cmp__ (self, other):
		if type(other)!=type(self):
			return -1
		c = cmp(self.start_time, other.start_time)
		if c==0:
			c = cmp(self.ID, other.ID)
		if c==0:
			c = StatefulObject.__cmp__(self, other)
		return c

	def init_transients(self):
		self.default_icon_data = None
		self.default_icon = None
		self.window_icon_data = None
		self.window_icon = None
		self.screen_capture_icon_data = None
		self.screen_capture_icon = None

		self.env = None


	def validate(self):
		assert self.display
		assert len(self.display)>=2
		if self.session_type in [WINDOWS_TYPE, SCREEN_TYPE, LIBVIRT_TYPE, OSX_TYPE, VIRTUALBOX_TYPE]:
			return
		assert self.display[0] == ":"
		if len(self.display)>4 and self.display[1]=="S" and self.display[2]=="-":
			#hack for screen sessions... format: ":S-$PID"
			int(self.display[3:])
		else:
			int(self.display[1:])

	def is_shadow(self):
		return self.shadowed_display is not None and len(self.shadowed_display)>0

	def can_have_actor(self):
		return self.session_type not in [X11_TYPE, SSH_TYPE, OSX_TYPE]		#these sessions never move around - actor is always the same as the owner

	def can_do_sound(self):
		return self.session_type not in [SSH_TYPE, LIBVIRT_TYPE, SCREEN_TYPE, OSX_TYPE]		#sound currently not supported for those session types

	def can_be_shadowed(self, shadow_type):
		if self.is_shadow():
			return	False
		assert shadow_type in [VNC_TYPE, NX_TYPE, XPRA_TYPE, GSTVIDEO_TYPE]
		if self.session_type==OSX_TYPE:
			return	True
		if self.session_type==VIRTUALBOX_TYPE:
			return	False
		if self.session_type==WINDOWS_TYPE:
			if not self.actor or self.actor!=self.owner:	#owner!=actor means RDP is running, VNC would crash. No actor means locked, VNC cant run.
				return	False
			return	shadow_type!=NX_TYPE					#windows can only be shadowed via VNC or GSTVIDEO
		if self.session_type==X11_TYPE:
			return	True									#X11 can be shadowed by all
		#VNC can be shadowed by both NX and VNC
		#NX can only be shadowed by itself (and only if running a full desktop)
		if self.session_type==VNC_TYPE or (shadow_type==NX_TYPE and self.session_type==NX_TYPE and self.full_desktop):
			parsed = parse_screensize(self.screen_size)
			if parsed is None:
				return	True			#FIXME: not specified... hope for the best!
			(_, _, depth) = parsed
			return	(depth is None) or (depth<0) or (depth>8)	#NX cant shadow 8-bit displays
		return	False

	def get_description(self):
		if self.full_desktop:
			if self.session_type==X11_TYPE or self.session_type==WINDOWS_TYPE or self.session_type==OSX_TYPE:
				title = "Physical Desktop: %s" % self.name
			else:
				title = "Virtual Desktop: %s" % self.name
		elif self.shadowed_display:
			title = "Shadow of %s" % self.name
		else:
			title = "Application: %s" % self.name
		return title

	def get_info(self):
		ro = ""
		if self.read_only:
			ro = "read-only "
		return	"%s (%s%s session)" % (self.status, ro, self.session_type)


	def is_connected_to(self, uuid):
		return	not self.timed_out and (self.status==Session.STATUS_CONNECTED or self.status==Session.STATUS_IDLE) and self.actor==uuid

	def is_connecting_to(self, uuid):
		return	not self.timed_out and self.status==Session.STATUS_CONNECTING and self.actor==uuid

	def do_set_status(self, new_status, touch=True):
		""" override so we can catch STATUS_CLOSED and call close() to clear the actor """
		StatefulObject.do_set_status(self, new_status, touch)
		if new_status==Session.STATUS_CLOSED:
			self.close()

	def close(self):
		self.actor = ""							#clear actor

	#window icon
	def get_window_icon(self):
		if not self.window_icon and self.window_icon_data:
			from winswitch.util.icon_util import get_icon_from_data
			self.window_icon = get_icon_from_data(self.window_icon_data)
			if not self.window_icon:
				self.serror("for %s, invalid data!" % self)
				self.window_icon_data = None
		return self.window_icon

	def get_window_icon_data(self):
		if not self.window_icon_data and self.window_icon:
			from winswitch.util.icon_util import get_data_from_icon
			self.window_icon_data = get_data_from_icon(self.window_icon)
		return self.window_icon_data

	def set_window_icon_data(self, data):
		self.window_icon = None
		self.window_icon_data = data

	#window icon
	def get_screen_capture_icon(self):
		if not self.screen_capture_icon and self.screen_capture_icon_data:
			from winswitch.util.icon_util import get_icon_from_data
			self.screen_capture_icon = get_icon_from_data(self.screen_capture_icon_data)
			if not self.screen_capture_icon:
				self.serror("for %s, invalid data!" % self)
				self.screen_capture_icon_data = None
		return self.screen_capture_icon

	def get_screen_capture_icon_data(self):
		if not self.screen_capture_icon_data and self.screen_capture_icon:
			from winswitch.util.icon_util import get_data_from_icon
			self.screen_capture_icon_data = get_data_from_icon(self.screen_capture_icon)
		return self.screen_capture_icon_data

	def set_screen_capture_icon_data(self, data):
		self.screen_capture_icon = None
		self.screen_capture_icon_data = data

	#default icon
	def get_default_icon(self):
		if not self.default_icon and self.default_icon_data:
			from winswitch.util.icon_util import get_icon_from_data
			self.default_icon = get_icon_from_data(self.default_icon_data)
			if not self.default_icon:
				self.serror("for %s, invalid data!" % self)
				self.default_icon_data = None
		return self.default_icon

	def get_default_icon_data(self):
		if not self.default_icon_data and self.default_icon:
			from winswitch.util.icon_util import get_data_from_icon
			self.default_icon_data = get_data_from_icon(self.default_icon)
		return self.default_icon_data

	def set_default_icon_data(self, data):
		self.default_icon = None
		self.default_icon_data = data
