import socket
import struct
import subprocess
import pickle
import ssl
import logging
import logging.config
import yaml
import sys
import os
import errno
from threading import Thread
import SocketServer

import ConfigParser

try:
	with open('logging.config') as f:
		logging.config.dictConfig(yaml.load(f))
	logging.captureWarnings(True)
except Exception as e:
	print (
		"Failed to load logging configuration: %s. "
		"No logging will be performed." % (
			str(e)
			)
	)

logger = logging.getLogger('parallels')

config_parser = ConfigParser.ConfigParser()
config_filename = 'config.ini'

class ServerConfiguration(object):
	def __init__(self):
		self.use_ssl = False
		self.client_cert = None
		self.server_cert = None
		self.server_key = None
		self.port = None

	def __repr__(self):
		return (
			"ServerConfiguration("
			"use_ssl={use_ssl}, "
			"client_cert={client_cert}, "
			"server_cert={server_cert}, "
			"server_key={server_key}, "
			"port={port}"
			")".format(
				use_ssl=self.use_ssl, 
				client_cert=self.client_cert, 
				server_cert=self.server_cert, 
				server_key=self.server_key, 
				port=self.port
				)
		)

try:
	config_parser.read(config_filename)
	configuration = ServerConfiguration()
	configuration.use_ssl = config_parser.getboolean('GLOBAL', 'use-ssl')
	configuration.client_cert = config_parser.get('GLOBAL', 'client-cert')
	configuration.server_cert = config_parser.get('GLOBAL', 'server-cert')
	configuration.server_key = config_parser.get('GLOBAL', 'server-key')
	configuration.port = config_parser.getint('GLOBAL', 'port')
except Exception as e:
	logger.debug(u'Exception:', exc_info=e)
	logger.info("Failed to read configuration file '%s': %s", config_filename, e)
	sys.exit(1)

logger.debug("Server configuration: %r", configuration)

class LocalRunner(object):
	def sh_unchecked(self, cmd_str, args=None, stdin_content=None):
		if args is not None:
			cmd_str = self._format_command(cmd_str, **args)

		p = subprocess.Popen(cmd_str, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		stdout_str, stderr_str = [
			s.decode('utf-8', 'strict') 
			for s in p.communicate(stdin_content)
		]
		logger.debug(u"Command stdout: %s" % stdout_str)
		logger.debug(u"Command stderr: %s" % stderr_str)
		logger.debug(u"Command exit code: %s" % p.returncode)
		return p.returncode, stdout_str, stderr_str

	def get_file_contents(self, remote_filename):
		with open(remote_filename, 'rb') as fp:
			return fp.read()

	def upload_file_content(self, filename, contents):
		with open(filename, "wb") as fp:
			fp.write(contents)

	def mkdir(self, dirname):
		try:
			os.makedirs(dirname)
		except OSError as exc:
			if exc.errno == errno.EEXIST:
				pass
			else:
				raise

	def remove_file(self, filename):
		try:
			os.remove(filename)
		except OSError as e:
			# if file does not exist - just skip
			if e.errno != errno.ENOENT:
				raise

	def _format_command(self, template, *args, **kw):
		qargs = map(self._quote_arg, args)
		qkw = dict((k, self._quote_arg(unicode(v))) for k, v in kw.iteritems())
		return unicode(template).format(*qargs, **qkw)

	@staticmethod
	def _quote_arg(arg):
		return '"%s"' % arg.replace('"', '""')

class ConnectionHandler(SocketServer.BaseRequestHandler):
	def handle(self):
		client_address = self.client_address[0]
		logger.info("Connected by '%s'", client_address)
		runner = LocalRunner()

		while True:
			length = self._receive(4)
			if not length:
				break
			length, = struct.unpack("I", length)
			command = self._receive(length)
			if not command:
				break
			function, args, kwargs = pickle.loads(command)
			result = pickle.dumps(getattr(runner, function)(*args, **kwargs))
			self.request.sendall(struct.pack("I", len(result)))
			self.request.sendall(result)

		logger.info("Finished connection from '%s'", client_address)

	def _receive(self, size):
		b = ''
		while len(b) < size:
			r = self.request.recv(size - len(b))
			if not r:
				return b
			b += r
		return b

class AgentServer(SocketServer.TCPServer):
	def __init__(self, configuration):
		SocketServer.TCPServer.__init__(
			self,
			('', configuration.port), ConnectionHandler, 
			bind_and_activate=False
		)
		self.configuration = configuration
		if self.configuration.use_ssl:
			self.socket = ssl.wrap_socket(
				self.socket, server_side=True, 
				keyfile=self.configuration.server_key, 
				certfile=self.configuration.server_cert, 
				cert_reqs=ssl.CERT_REQUIRED, 
				ca_certs=configuration.client_cert
			)

		self.server_bind()
		self.server_activate()

server = AgentServer(configuration)
server.serve_forever()
