#!/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 os

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

debug_import("user_base")
from winswitch.objects.user_base import UserBase
debug_import("crypt_util")
from winswitch.util.crypt_util import recreate_key, generate_key
debug_import("globals")
from winswitch.globals import USERNAME, NAME, LOCAL_IPP_PORT, WIN32, OSX, LINUX
debug_import("common")
from winswitch.util.common import is_valid_file, get_default_ssh_keyfile, get_default_ssh_pub_keyfile, check_xpra_version, generate_UUID, screensizes_to_strings
debug_import("consts")
from winswitch.consts import MODIFIER_KEY_SHIFT, DEFAULT_SCREEN_SIZE, DEFAULT_SCREEN_SIZES, DEFAULT_INTERNET_SPEED, DEFAULT_LAN_SPEED, BINASCII, BASE64, OPEN_LOCALLY, OPEN_NEW_SESSION
debug_import("commands_util")
from winswitch.util.commands_util import XPRA_COMMAND, VNC_COMMAND, SSH_COMMAND, NXPROXY_COMMAND, XMING_COMMAND, XTERM_COMMAND, WINSWITCH_SERVER_COMMAND, RDESKTOP_COMMAND, XFREERDP_COMMAND
debug_import("xmodmap_util")
from winswitch.util.xmodmap_util import get_xmodmap
debug_import("net_util")
from winswitch.net.net_util import get_interface_speed
debug_import("tz_util")
from winswitch.util.tz_util import get_tz_util
debug_import("all done")

"""
use native Windows API via py32ui rather than gtk.StatusIcon
better because we can then use native API for notifications (they require the "hwnd", which is only available via native tray API...)
Also needed to be able to monitor session events (also needs hwnd)
"""
PREFER_GTK_TRAY = False
WINDOW_NOTRAY = False
if not (WIN32 or OSX):
	de = os.environ.get("DESKTOP_SESSION") or os.environ.get("XDG_CURRENT_DESKTOP")
	if de in ["fluxbox"]:
		WINDOW_NOTRAY = True
DEFAULT_DEEP_MENUS = True 		#menus are fiddly on win32 as the tray is on the right side and gtk menu unrolls left to right

WRAPPER_MODIFIER_ENABLE = "Enable"
WRAPPER_MODIFIER_DISABLE = "Disable"
WRAPPER_MODIFIER_DIALOG = "Show Dialog"

WRAPPER_MODIFIER_OPTIONS = [WRAPPER_MODIFIER_ENABLE, WRAPPER_MODIFIER_DISABLE, WRAPPER_MODIFIER_DIALOG]

TRAY_ICON_NAMES = {"Auto" : "auto",
					"Grey Icon" : "winswitch_grey",
					"Blue Icon" : "winswitch_logo",
					"Red Icon" : "winswitch_attention",
					"Translucent Icon" : "winswitch_indicator",
					"Light Blue Icon" : "winswitch"}

settings = None
def	get_settings(can_create=False, skip_detection=False):
	global settings
	if not settings:
		if can_create:
			settings = GlobalSettings(skip_detection)
		else:
			raise Exception("Settings not set!")
	return	settings


class GlobalSettings(UserBase):
	"""
	This is the singleton class holding the settings for the client instance.
	"""

	PERSIST = [
			"# identification:",
			"uuid", "name",
			"# connect to all remote servers on startup:",
			"auto_connect",
			"# paths:",
			"xpra_command", "ssh_command", "nxproxy_command", "vnc_command", "rdesktop_command", "xterm_command",
			"# for disabling protocols:",
			"supports_xpra", "supports_ssh", "supports_nx", "supports_vnc", "supports_rdp", "supports_screen", "supports_gstvideo", "supports_virtualbox",
			"# user interface:",
			"advanced",
			"max_session_menu_items", "max_user_menu_items", "max_command_menu_items", "show_send_to_self",
			"max_menu_entry_length", "show_deep_menus", "show_user_host",
			"notifications", "notifications_prefer_growl",
			"menu_size",
			"icons_in_menus", "icons_in_buttons",
			"idle_timeout",
			"modifier_key", "modifier_required", "wrapper_modifier_action",
			"default_server",
			"# ssh authentication defaults:",
			"ssh_keyfile", "ssh_pub_keyfile", "encrypted_ssh_keyfile_passphrase",
			"# authentication key (secret!):",
			"crypto_modulus", "crypto_public_exponent", "crypto_private_exponent",
			"# default session types and attributes:",
			"preferred_session_type", "preferred_desktop_type",
			"default_desktop_size", "desktop_sizes",
			"timezone_options", "default_timezone",
			"hide_suboptimal_protocols",
			"# how to deal with requests to open files/URLs on servers:",
			"open_urls", "open_files",
			"# defaults used for new server configurations:",
			"default_internet_speed", "default_lan_speed",
			"# warnings:",
			"ignore_missing_mdns",
			"# networking / mDNS:",
			"mdns_listener",
			"mdns_auto_connect",
			"mdns_match_username", "mdns_match_username_string",
			"# start a local server:",
			"start_local_server", "server_command", "embedded_server",
			"# show some notifications when we connect to the local server",
			"notify_local_server",
			"# port forwaring:",
			"local_samba_port", "local_printer_port",
			"tunnel_fs", "tunnel_sink", "tunnel_source", "tunnel_clone", "tunnel_printer",
			"# file sharing:",
			"mount_points",
			"# gstreamer sound configuration for receiving/exporting sound from/to the local display:",
			"gst_sound_sink_module", "gst_sound_sink_options", "gst_sound_source_module", "gst_sound_source_options", "gst_sound_clone_module", "gst_sound_clone_options",
			"default_gstaudio_codec",
			"# gstreamer video configuration:",
			"# known options: autovideosink (all), ximagesink (*nix), xvimagesink (*nix with XVideo), directdrawsink (win32), directshowsink (win32), d3dvideosink (win32)",
			"gstvideo_sink_plugin",
			"# debugging:",
			"debug_mode",
			"# software update check:",
			"version_update_check",
			"# use custom xmodmap files (you must specify both - leave them empty to detect at runtime):",
			"# for example, you can generate those files with:",
			"# xmodmap -pke > ~/.winswitch/client/xmodmap_pke.conf",
			"# xmodmap -pm > ~/.winswitch/client/xmodmap_pm.conf",
			"# echo 'custom_xmodmap_pke=\"~/.winswitch/client/xmodmap_pke.conf\"' >> ~/.winswitch/client/client.conf",
			"# echo 'custom_xmodmap_pm=\"~/.winswitch/client/xmodmap_pm.conf\"' >> ~/.winswitch/client/client.conf",
			"custom_xmodmap_pm", "custom_xmodmap_pke",
			]
	if WIN32:			#only used on win32:
		PERSIST.append("xming_command")
		PERSIST.append("shared_xming_display")
		PERSIST.append("create_start_menu_folders")
		PERSIST.append("windows_edition_warning")
	if LINUX:
		PERSIST.append("selinux_enabled_warning")
	if not WIN32 and not OSX:
		PERSIST.append("window_notray")
	if not OSX:
		PERSIST.append("# Using the default gtk.StatusIcon is not recommended on MS Windows")
		PERSIST.append("# On some systems (Ubuntu), it may be preferable to 'appindicator' when the latter does not work")
		PERSIST.append("prefer_gtk_tray")
		PERSIST.append("tray_icon_name")

	def __init__(self, skip_detection=False):
		UserBase.__init__(self)
		self.name = NAME

		self.version_update_check = not LINUX
		self.selinux_enabled_warning = 3				#notify the user until this reaches zero
		self.windows_edition_warning = 2
		self.crypto_private_exponent = 0l

		self.xpra_command = XPRA_COMMAND
		self.ssh_command = SSH_COMMAND
		self.nxproxy_command = NXPROXY_COMMAND
		self.vnc_command = VNC_COMMAND
		self.rdesktop_command = RDESKTOP_COMMAND or XFREERDP_COMMAND
		self.xming_command = XMING_COMMAND
		self.shared_xming_display = True
		self.xterm_command = XTERM_COMMAND

		self.server_command = "%s" % WINSWITCH_SERVER_COMMAND
		self.start_local_server = skip_detection or is_valid_file(self.server_command)
		self.auto_connect = True
		self.embedded_server = True
		self.mdns_listener = True
		self.mdns_auto_connect = True
		self.mdns_match_username = False
		self.mdns_match_username_string = "*%s*" % USERNAME
		self.ignore_missing_mdns = False
		self.notifications = True
		self.notifications_prefer_growl = True
		self.default_server = ""
		self.default_internet_speed = DEFAULT_INTERNET_SPEED
		self.default_lan_speed = DEFAULT_LAN_SPEED

		self.idle_timeout = 0				#1/0 on win32, delay on X
		self.default_desktop_size = "%sx%s" % DEFAULT_SCREEN_SIZE
		self.timezone_options = get_tz_util().get_timezone_options()
		self.default_timezone = get_tz_util().get_local_timezone()
		self.desktop_sizes = screensizes_to_strings(DEFAULT_SCREEN_SIZES)
		self.hide_suboptimal_protocols = True
		self.open_urls = OPEN_LOCALLY			# or "new-session" or "existing-session"
		self.open_files = OPEN_NEW_SESSION		# or "existing-session"
		#Video
		self.gstvideo_sink_plugin = "autovideosink"

		#Sound:
		self.supports_sound = True
		if not self.supports_sound:
			self.tunnel_sink = False
			self.tunnel_source = False
			self.tunnel_clone = False
		if skip_detection or not self.supports_sound:
			self.gst_sound_sink_module = ""
			self.gst_sound_sink_options = {}
			self.gst_sound_source_module = ""
			self.gst_sound_source_options = {}
			self.gst_sound_clone_module = ""
			self.gst_sound_clone_options = {}
			self.default_gstaudio_codec = ""
		else:
			self.set_gst_attributes()

		#UI:
		self.tray_icon_name = "auto"
		self.window_notray = WINDOW_NOTRAY					#for systems without a tray area
		self.prefer_gtk_tray = PREFER_GTK_TRAY				#On Ubuntu, this may be helpful when appindicator does not work
		self.show_deep_menus = DEFAULT_DEEP_MENUS and not self.window_notray
		self.max_session_menu_items = 5
		self.max_user_menu_items = 3
		self.max_command_menu_items = 5
		self.max_menu_entry_length = 64
		self.modifier_required = True
		self.modifier_key = MODIFIER_KEY_SHIFT
		self.wrapper_modifier_action = WRAPPER_MODIFIER_DISABLE
		self.show_send_to_self = True
		self.show_user_host = True
		self.notify_local_server = False
		self.menu_size = -1
		self.icons_in_menus = True
		self.icons_in_buttons = True
		self.create_start_menu_folders = False			#only used on win32

		self.advanced = False
		self.debug_mode = False

		self.key_fingerprint = ""
		self.ssh_keyfile_passphrase = ""
		self.encrypted_ssh_keyfile_passphrase = ""
		if skip_detection:
			self.ssh_keyfile = ""
			self.ssh_pub_keyfile = ""
		else:
			self.ssh_keyfile = get_default_ssh_keyfile()
			self.ssh_pub_keyfile = get_default_ssh_pub_keyfile()

		self.local_printer_port = LOCAL_IPP_PORT		#631
		self.local_samba_port = 139						#unless user changed smb.conf

		self.custom_xmodmap_pm = ""
		self.custom_xmodmap_pke = ""
		self.xmodmap_keys = None
		self.xmodmap_modifiers = None
		self.on_populate_xmodmap = []
		self.binary_encodings = [BINASCII, BASE64]

		#transients:
		self.is_new = False
		self.xpra_version = ""
		self.reset_workrounds()

	def populate_xmodmap(self):
		""" populates the xmodmap data in a new thread
			(but the 'on_populate_xmodmap' callbacks are called from the main thread)
		"""
		import thread
		thread.start_new(self.do_populate_xmodmap, ())

	def do_populate_xmodmap(self):
		self.xmodmap_keys, self.xmodmap_modifiers = get_xmodmap()
		for x in self.on_populate_xmodmap:
			try:
				from winswitch.util.main_loop import callFromThread
				callFromThread(x)
			except Exception, e:
				self.serr(None, e)

	def set_gst_attributes(self):
		from winswitch.util.gstreamer_util import get_default_gst_sound_sink_module, get_default_gst_sound_sink_options,\
								get_default_gst_sound_source_module, get_default_gst_sound_source_options,\
								get_default_gst_clone_module, get_default_gst_sound_clone_options, \
								has_gst, has_tcp_plugins, supported_gstaudio_codecs, supported_gstvideo_codecs, get_default_gst_video_sink_module
		from winswitch.sound.sound_util import has_pa
		self.supports_sound = has_gst and has_tcp_plugins and len(supported_gstaudio_codecs)>0
		self.tunnel_sink = self.supports_sound
		self.tunnel_source = self.supports_sound
		self.tunnel_clone = self.supports_sound and has_pa()
		self.gst_sound_sink_module = get_default_gst_sound_sink_module()
		self.gst_sound_sink_options = get_default_gst_sound_sink_options(self.gst_sound_sink_module)
		self.gst_sound_source_module = get_default_gst_sound_source_module()
		self.gst_sound_source_options = get_default_gst_sound_source_options(self.gst_sound_source_module)
		self.gst_sound_clone_module = get_default_gst_clone_module()
		self.gst_sound_clone_options = get_default_gst_sound_clone_options(self.gst_sound_clone_module)
		self.gstvideo_sink_plugin = get_default_gst_video_sink_module()
		self.gstaudio_codecs = supported_gstaudio_codecs
		self.gstvideo_codecs = supported_gstvideo_codecs
		if len(self.gstaudio_codecs)>0:
			self.default_gstaudio_codec = self.gstaudio_codecs[0]
		else:
			self.default_gstaudio_codec = ""


	def get_desktop_sizes(self):
		size_list = []
		for s in self.desktop_sizes:
			pos = s.find("x")
			if pos>0:
				w = s[:pos]
				h = s[pos+1:]
				size_list.append((w,h))
		return	size_list

	def reset_workrounds(self):
		self.sdebug()
		self.icons_in_menus_workaround_applied = False
		self.icons_in_buttons_workaround_applied = False

	def detect_lan_speed(self):
		""" Try to detect local LAN interface speed if available: """
		self.default_lan_speed = get_interface_speed(-1, None, DEFAULT_LAN_SPEED)
		if self.default_internet_speed>self.default_lan_speed:
			self.default_internet_speed = self.default_lan_speed

	def detect_xpra_version(self):
		self.xpra_version = check_xpra_version(self.xpra_command)

	def test_supports_xpra(self):
		return	self.supports_xpra and is_valid_file(self.xpra_command)

	def test_supports_nx(self):
		return self.supports_nx and is_valid_file(self.nxproxy_command) and self._test_supports_Xdisplay()

	def test_supports_vnc(self):
		return	self.supports_vnc and is_valid_file(self.vnc_command)

	def test_supports_gstvideo(self):
		from winswitch.util.gstreamer_util import has_tcp_plugins, get_default_gst_video_sink_module
		return has_tcp_plugins and get_default_gst_video_sink_module() is not None

	def test_supports_rdp(self):
		#on win32 rdesktop_command="mstsc" which is not a full path but works ok
		return	self.supports_rdp and (WIN32 or is_valid_file(self.rdesktop_command))

	def test_supports_screen(self):
		return	self.supports_screen and is_valid_file(self.xterm_command) and self._test_supports_Xdisplay()



	def has_Xming(self):
		return	is_valid_file(self.xming_command)

	def has_ssh(self):
		return	is_valid_file(self.ssh_command)

	def test_supports_sshX(self):
		return self.supports_ssh and self.has_ssh() and self._test_supports_Xdisplay()

	def _test_supports_Xdisplay(self):
		"""
		Tests whether we can launch applications that rely on a local display.
		"""
		if WIN32:
			return	self.has_Xming()			#on win32: we need Xming to get a local X display
		return "DISPLAY" in os.environ			#on other platforms, DISPLAY should be set

	def disable_flags_for_missing_commands(self):
		if not self.test_supports_nx():
			self.supports_nx = False
		if not self.test_supports_sshX():
			self.supports_ssh = False
		if not self.test_supports_vnc():
			self.supports_vnc = False
		if not self.test_supports_rdp():
			self.supports_rdp = False
			self.supports_virtualbox = False
		if not self.test_supports_xpra():
			self.supports_xpra = False
		if not self.test_supports_screen():
			self.supports_screen = False
		if not self.test_supports_gstvideo():
			self.supports_gstvideo = False


	def get_available_session_types(self, desktop_only=False, hide_suboptimal=None):
		if hide_suboptimal is None:
			hide_suboptimal = self.hide_suboptimal_protocols
		xpra_enabled = self.test_supports_xpra()
		nx_enabled = self.test_supports_nx()
		ssh_enabled = self.test_supports_sshX()
		vnc_enabled = self.test_supports_vnc()
		rdp_enabled = self.test_supports_rdp()
		libvirt_enabled = vnc_enabled		#client only needs vnc
		virtualbox_enabled = rdp_enabled	#client only needs rdp
		from winswitch.objects.session import do_get_available_session_types
		#def do_get_available_session_types(xpra, xpra_desktop, nx, ssh, ssh_desktop, vnc, libvirt, vbox, rdp, rdp_desktop, desktop_mode, hide_suboptimal):
		return	do_get_available_session_types(xpra_enabled, xpra_enabled,
												nx_enabled,
												ssh_enabled, ssh_enabled,
												vnc_enabled,
												libvirt_enabled, virtualbox_enabled,
												rdp_enabled, rdp_enabled,
													desktop_only, hide_suboptimal)

	def __str__(self):
		return	"GlobalSettings(%s)" % self.uuid

	def assign_keys(self):
		self.uuid = "%s" % generate_UUID()
		self.key = generate_key()
		self.crypto_modulus = self.key.n
		self.crypto_public_exponent = self.key.e
		self.crypto_private_exponent = self.key.d
		self.regenerate_key_fingerprint()
		self.is_new = True

	def get_key(self):
		if not self.key:
			self.key = recreate_key(self.crypto_modulus, self.crypto_public_exponent, self.crypto_private_exponent)
		return self.key
