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

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


debug_import("globals")
from winswitch.globals import USER_ID
debug_import("global_settings")
from winswitch.objects.global_settings import GlobalSettings, get_settings
debug_import("server_config")
from winswitch.objects.server_config import ServerConfig
debug_import("server_base")
from winswitch.objects.server_base import ServerBase
debug_import("mount_point")
from winswitch.objects.mount_point import MountPoint
debug_import("common")
from winswitch.util.common import alphanumfile, is_valid_file, no_newlines, save_binary_file, load_binary_file, delete_if_exists, visible_command
debug_import("crypt_util")
from winswitch.util.crypt_util import decrypt_hex
debug_import("file_io")
from winswitch.util.file_io import get_client_config_filename, get_servers_config_dir, get_exports_config_dir, \
	get_local_server_config_filename, get_server_config_filename, get_avatar_icon_file, \
	save_object_to_properties, load_object_from_properties, get_object_stored_as_filename, modify_object_properties, load_properties, \
	get_session_icon_filename, get_session_filename, get_user_id, get_client_session_pid_file, get_server_signatureimage_filename
debug_import("paths")
from winswitch.util.paths import SERVER_CONFIG_EXTENSION
debug_import("all done")

logger = Logger("config", log_colour=Logger.CYAN)



def make_server_config_from_settings(settings):
	""" Convert a ServerSettings instance to a ServerConfig """
	server = ServerConfig()
	for key in ServerBase.PERSIST_COMMON:
		if key.startswith("#"):
			continue
		value = getattr(settings, key)
		setattr(server, key, value)
	return	server


#
# save/load GlobalSettings
#
def modify_settings(settings, fields):
	config_file = get_client_config_filename()
	modify_object_properties(config_file, settings, fields)

def save_settings(settings):
	# global settings
	config_file = get_client_config_filename()
	save_object_to_properties(config_file, settings, chmod=stat.S_IREAD | stat.S_IWRITE)

def load_uuid_from_settings():
	config_file = get_client_config_filename()
	props = load_properties(config_file)
	return props.get("uuid")

def load_settings(keys_in=None, load_details=True, warn_on_missing_keys=True):
	# global settings
	config_file = get_client_config_filename()
	settings = load_object_from_properties(config_file, GlobalSettings, keys_in=keys_in, constructor=lambda:get_settings(True,True), newerversion_constructor=lambda:get_settings(True,False), warn_on_missing_keys=warn_on_missing_keys)
	if settings and load_details:
		settings.avatar_icon_data = load_avatar(settings)
		settings.mount_points = load_exports()
		load_encrypted_field(settings, "ssh_keyfile_passphrase", settings)
	return	settings


#
# Avatar (from GlobalSettings)
#
def load_avatar(settings):
	return load_binary_file(get_avatar_icon_file())

def save_avatar(settings):
	save_binary_file(get_avatar_icon_file(), settings.avatar_icon_data)


#
# load/save MountPoints
#
def save_exports(settings):
	for mp in settings.mount_points:
		filename = "%s_%s_%s.mount" % (alphanumfile(mp.protocol), alphanumfile(mp.host), alphanumfile(mp.path))
		config_file = os.path.join(get_exports_config_dir(), filename)
		save_object_to_properties(config_file, mp, chmod=stat.S_IREAD | stat.S_IWRITE)

def load_exports_from(exports_dir):
	"""
	Loads all the valid MountPoint configuration files in the given directory.
	"""
	mount_points = []
	if not os.path.exists(exports_dir) or not os.path.isdir(exports_dir):
		return	mount_points
	all_files = os.listdir(exports_dir)
	for filename in all_files:
		full_path = os.path.join(exports_dir, filename)
		loaded = load_object_from_properties(full_path, MountPoint)
		if loaded and loaded.protocol:
			mount_points.append(loaded)
	return mount_points

def load_exports():
	return load_exports_from(get_exports_config_dir())

#
# load/save ServerConfigs
#
def modify_server_config(server_config, fields):
	filename = get_server_config_filename(server_config)
	if is_valid_file(filename):
		modify_object_properties(get_server_config_filename(server_config), server_config, fields)
	else:
		save_server_config(server_config)

def save_server_config(server_config, delete_old=True):
	if delete_old:
		old_filename = get_object_stored_as_filename(server_config)
		if old_filename:
			delete_if_exists(old_filename)
	filename = get_server_config_filename(server_config)
	save_object_to_properties(filename, server_config)

def delete_server_config(server_config):
	filename = get_object_stored_as_filename(server_config)
	if filename:
		delete_if_exists(filename)

def load_server_config(ID):
	servers = load_server_configs(ID, True)
	if len(servers)>0:
		return	servers[0]
	return	None

def load_server_configs(ID=None, include_dynamic=False):
	"""
	If include_dynamic is not True, local servers and servers found through mDNS
	will not be returned.
	This prevents us from showing servers that might not be present.
	These can be loaded	on demand when needed.
	We can also filter by server.ID
	"""
	servers = []
	server_dir = get_servers_config_dir()
	dir_list = os.listdir(server_dir)
	for filename in dir_list:
		if not filename.endswith(SERVER_CONFIG_EXTENSION):
			continue
		full_filename = os.path.join(server_dir, filename)
		server = load_server_config_from_file(full_filename, False)
		logger.sdebug("server(%s)=%s, modulus=%s, exponent=%s" % (full_filename, server, visible_command(str(server.crypto_modulus)), visible_command(str(server.crypto_public_exponent))), ID, include_dynamic)
		if ID:
			if server.ID!=ID:
				continue
		if server and (include_dynamic or (not server.dynamic and not server.local)):
			servers.append(server)
	return servers

def load_server_config_from_file(filename, try_plain_password=False):
	if try_plain_password:
		extra_keys = ["password", "ssh_keyfile_passphrase"]
	else:
		extra_keys = []
	server = load_object_from_properties(filename, ServerConfig, extra_keys=extra_keys)
	#load encrypted values:
	load_encrypted_field(server, "password", get_settings())
	load_encrypted_field(server, "ssh_keyfile_passphrase", get_settings())
	load_server_signatureimage(server)
	return server

def load_server_signatureimage(server):
	signatureimage = get_server_signatureimage_filename(server)
	exists = is_valid_file(signatureimage)
	logger.sdebug("is_valid_file(%s)=%s" % (signatureimage, exists), server)
	if exists:
		try:
			from winswitch.util.icon_util import load_pixmap_file
			server.key_fingerprint_image = load_pixmap_file(signatureimage)
		except Exception, e:
			logger.serror("failed to load %s: %s" % (signatureimage, e), server)


def load_encrypted_field(config_object, base_field_name, settings):
	enc_field_name = "encrypted_%s" % base_field_name
	#field may be set already:
	raw = getattr(config_object, base_field_name)			#ie: server.password
	if raw:
		set_raw_fn = getattr("set_%s" % base_field_name)	#ie: set_password()
		if not set_raw_fn:
			raise Exception("cannot find set method for %s in %s" % (base_field_name, config_object))
		set_raw_fn(config_object, raw)						#ie: server.set_password(raw)
	#now try the encrypted field:
	enc = getattr(config_object, enc_field_name)			#ie: server.encrypted_password
	if enc:
		fingerprint = "%s:" % settings.get_key_fingerprint()
		if not enc.startswith(fingerprint):
			logger.serror("encryption used a different key, cannot decrypt it!", config_object, base_field_name)
		else:
			enc_trim = enc[len(fingerprint):]
			decoded = decrypt_hex(get_settings().get_key(), enc_trim)
			setattr(config_object, base_field_name, decoded)


def get_server_config(servers, ID, try_from_disk=True):
	"""
	Returns the server config matching ID from the list supplied,
	or from disk if that fails. (if try_from_disk is True)
	"""
	server = find_server_config(servers, ID)
	if server:
		return	server
	return	load_server_config(ID)

def find_server_config(configs, ID):
	"""
	All this code does is choose the best option for when multiple configs
	have the same ID... but this shouldn't happen..
	"""
	candidates = []
	for server in configs:
		if server.ID == ID:
			if server.enabled and server.is_connected():
				return server
			if server.enabled or server.is_connected():
				candidates.insert(0, server)
			else:
				candidates.append(server)
	if len(candidates)>0:
		return	candidates[0]
	return None


#
# load/save Sessions
#
def load_session(display, load_icon, session_constructor):
	session_file = get_session_filename(display)
	return load_session_from_file(session_file, load_icon, session_constructor)

def load_session_from_file(session_file, load_icon, session_constructor):
	if not is_valid_file(session_file):
		logger.sdebug("file does not exist", session_file, load_icon, session_constructor)
		return	None
	try:
		session = load_object_from_properties(session_file, session_constructor)
		if session:
			session.init_transients()
			if load_icon:
				session.set_window_icon_data(load_session_icon(session.display))
		return session
	except Exception, e:
		logger.serr(None, e, session_file, load_icon, session_constructor)
	return None

def load_session_icon(display):
	filename = get_session_icon_filename(display)
	data = load_binary_file(filename)
	logger.sdebug("loaded %d from %s" % (len(data), filename), display)
	return data

def save_session(session):
	session_file = get_session_filename(session.display, session.user, USER_ID==0)
	save_session_to_file(session, session_file)
	return	session_file

def save_session_to_file(session, filename):
	save_object_to_properties(filename, session)
	if USER_ID==0 and get_user_id:
		#FIXME: dangerous to chown file.. could have been symlinked (should use tempfile and move it)
		user_id = get_user_id(session.user)
		os.chown(filename, user_id, 0)
		os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR)	# | stat.S_IRGRP
	save_session_icon(session)

def save_session_icon(session):
	data = session.get_window_icon_data() or session.get_default_icon_data()
	filename = get_session_icon_filename(session.display)
	save_binary_file(filename, data)

def delete_client_pid(session):
	filename = get_client_session_pid_file(session.ID)
	delete_if_exists(filename)

def get_client_pid(session):
	filename = get_client_session_pid_file(session.ID)
	data = load_binary_file(filename)
	if not data:
		return	None
	return	int(data)

def save_client_pid(session):
	filename = get_client_session_pid_file(session.ID)
	if session.client_pid:
		data = "%s" % session.client_pid
	else:
		data = ""
	save_binary_file(filename, data)




#
# load/save ServerSettings
#
def modify_local_server_config(config, fields):
	filename = get_local_server_config_filename()
	modify_object_properties(filename, config, fields)

def save_local_server_config(config):
	filename = get_local_server_config_filename()
	save_object_to_properties(filename, config, chmod=stat.S_IREAD | stat.S_IWRITE)

def get_local_server_config(as_root=False, resave_newerversion=False, show_warnings=True, load_ssh_key=True):
	filename = get_local_server_config_filename(as_root)
	from winswitch.objects.server_settings import ServerSettings
	conf = load_object_from_properties(filename, ServerSettings, constructor=lambda:ServerSettings(True), newerversion_constructor=lambda:ServerSettings(False), warn_on_missing_keys=show_warnings, warn_on_extra_keys=show_warnings, resave_newerversion=resave_newerversion)
	if conf:
		logger.sdebug("conf=%s, ssh_host_public_key_file=%s" % (conf, conf.ssh_host_public_key_file))
	if conf and load_ssh_key and conf.ssh_host_public_key_file:
		if not is_valid_file(conf.ssh_host_public_key_file):
			logger.serr("server config %s points to ssh key public file %s which does not exist!" % (filename, conf.ssh_host_public_key_file), None, as_root)
			return	conf
		ssh_pubkey_data_file = open(conf.ssh_host_public_key_file)
		for line in ssh_pubkey_data_file.readlines():
			if line and len(line)>0:
				conf.ssh_host_public_key = no_newlines(line)
		ssh_pubkey_data_file.close()
		logger.sdebug("loaded ssh public key file '%s'= %s" % (conf.ssh_host_public_key_file, conf.ssh_host_public_key), as_root)
	return	conf

