#!/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 base64
import re

from winswitch.consts import VNC_TYPE, DEFAULT_SCREEN_DEPTHS
from winswitch.globals import WIN32
from winswitch.objects.session import Session
from winswitch.util.common import save_binary_file, get_bool, hash_text
from winswitch.util.file_io import get_client_session_password_file
from winswitch.virt.client_util_base import ClientUtilBase
from winswitch.virt.options_common import CLIPBOARD, FULLSCREEN, JPEG_QUALITY_OPTION, COMPRESSION, READ_ONLY

BASE64_PREFIX = "base64:"	#in some cases (Xvnc, x11vnc) the VNC password is encoded as base64 as it is in binary form, not a string

class	VNCClientBase(ClientUtilBase):

	CONNECTED_RE				= re.compile(r"\s*CConn:\s*connected to host.*")
	CONNECTION_REFUSED_RE		= re.compile(r"\s*main:\s*unable connect to socket: Connection refused.*")
	CONNECTION_TIMEOUT_RE		= re.compile(r"\s*main:\s*unable connect to socket: Connection timed out.*")
	CONNECTION_ENDED_RE			= re.compile(r"\s*main:\s*End of stream.*")
	CONNECTION_AUTH_FAILURE_RE	= re.compile(r"\s*main:\s*Authentication failure.*")
	CONNECTION_READ_CLOSED_RE	= re.compile(r"\s*main:\s*read: Connection reset by peer.*")
	CONNECTION_WRITE_CLOSED_RE	= re.compile(r"\s*main:\s*write: Connection reset by peer.*")
	CONNECTION_INVALIDPASS_RE	= re.compile(r"\s*main:\s*bad obfuscated password length.*")

	def	__init__(self, update_session_status, notify_callback):
		ClientUtilBase.__init__(self, VNC_TYPE, update_session_status, notify_callback)
		self.ignore_process_returncode_after = 10			#ignore non-zero exit code after 10 seconds, we probably connected ok and the connection may be stopped by the other end
		#client output parsing:
		self.client_log_actions[VNCClientBase.CONNECTED_RE] 				= self.handle_line_connected
		self.client_log_actions[VNCClientBase.CONNECTION_REFUSED_RE]		= self.handle_line_connect_refused	#Kill the client before it gets a chance to display its annoying popup error box
		self.client_log_actions[VNCClientBase.CONNECTION_TIMEOUT_RE]		= self.handle_line_connect_timeout	#As above
		self.client_log_actions[VNCClientBase.CONNECTION_ENDED_RE]			= self.handle_line_log
		self.client_log_actions[VNCClientBase.CONNECTION_AUTH_FAILURE_RE]	= self.handle_line_auth_failure
		self.client_log_actions[VNCClientBase.CONNECTION_INVALIDPASS_RE]	= self.handle_line_auth_failure			#can happen when we use the old vncpasswd without "-f"
		self.client_log_actions[VNCClientBase.CONNECTION_READ_CLOSED_RE]	= self.handle_line_connection_closed	#server terminated? (as above)
		self.client_log_actions[VNCClientBase.CONNECTION_WRITE_CLOSED_RE]	= self.handle_line_connection_closed	#server terminated? (as above)
		self.desktop_bit_depths = DEFAULT_SCREEN_DEPTHS


	def do_real_attach(self, server, session, host, port):
		args = [self.settings.vnc_command]
		if session.password:
			password = session.password
			if password.startswith(BASE64_PREFIX):
				password = base64.b64decode(password[len(BASE64_PREFIX):])
			if WIN32 and len(password)>8:
				password = password[0:8]		#passwords longer than 8 characters fail on win32, even if they match the real password!
			password_file = get_client_session_password_file(session.ID)
			save_binary_file(password_file, password)
			self.slog("saved password '%s' in %s" % (hash_text(password), password_file), server, session, host, port)
			args.append("PasswordFile=%s" % password_file)
			if not WIN32:
				args.append("name=%s on %s" % (session.name, server.get_display_name()))		#TigerVNC on windows does not support "name"
		clipboard = session.options.get(CLIPBOARD)
		if clipboard is not None and not get_bool(clipboard):
			args.append("SendClipboard=0");
			args.append("AcceptClipboard=0");
		fullscreen = session.options.get(FULLSCREEN)
		if fullscreen is not None and get_bool(fullscreen):
			args.append("FullScreen=1")
		readonly = session.options.get(READ_ONLY)
		if readonly is not None and get_bool(readonly):
			args.append("ViewOnly=1")
		jpgq = session.options.get(JPEG_QUALITY_OPTION)
		if jpgq is not None:
			q = int(jpgq)
			if q<=0:
				args.append("NoJPEG=1")
			else:
				args.append("NoJPEG=0")
				args.append("QualityLevel=%s" % q)
		comp = session.options.get(COMPRESSION)
		if comp is not None:
			args.append("CompressLevel=%s" % comp)

		args.append("%s::%s" % (host, port))
		#ie: vncviewer PasswordFile=/home/antoine/.winswitch/client/sessions/61.pass localhost::16062
		self.exec_client(server, session, args, onstart_status=None, onexit_status=Session.STATUS_AVAILABLE)

	def client_detach_session(self, server, client, session):
		"""
		Override so we force the server to set the session state to "AVAILABLE".
		VNC session state transitions between CONNECTED and AVAILABLE are handled by
		the do_real_attach() method above, but if we re-started the client without
		re-setting the state to AVAILABLE it may never reach it.
		"""
		ClientUtilBase.client_detach_session(self, server, client, session)
		self.update_session_status(server, session, Session.STATUS_AVAILABLE)

	def get_options_defaults(self):
		return	{
				JPEG_QUALITY_OPTION: 8,
				COMPRESSION: 3,
				CLIPBOARD: True,
				FULLSCREEN: False,
				READ_ONLY: False,
				}
