#!/usr/bin/python3
import socket, logging, traceback
from gi.repository import GObject

from lib.commands import ControlServerCommands

class ControlServer():
	log = logging.getLogger('ControlServer')

	boundSocket = None

	def __init__(self, pipeline):
		'''Initialize server and start listening.'''
		self.commands = ControlServerCommands(pipeline)

		port = 9999
		self.log.debug('Binding to Command-Socket on [::]:%u', port)
		self.boundSocket = socket.socket(socket.AF_INET6)
		self.boundSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.boundSocket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
		self.boundSocket.bind(('::', port))
		self.boundSocket.listen(1)

		self.log.debug('Setting GObject io-watch on Socket')
		GObject.io_add_watch(self.boundSocket, GObject.IO_IN, self.on_connect)

	def on_connect(self, sock, *args):
		'''Asynchronous connection listener. Starts a handler for each connection.'''
		conn, addr = sock.accept()
		self.log.info("Incomming Connection from %s", addr)

		self.log.debug('Setting GObject io-watch on Connection')
		GObject.io_add_watch(conn, GObject.IO_IN, self.on_data)
		return True

	def on_data(self, conn, *args):
		'''Asynchronous connection handler. Processes each line from the socket.'''
		# construct a file-like object fro mthe socket
		# to be able to read linewise and in utf-8
		filelike = conn.makefile('rw')

		# read a line from the socket
		line = filelike.readline().strip()

		# no data = remote closed connection
		if len(line) == 0:
			self.log.info("Connection closed.")
			return False

		# 'quit' = remote wants us to close the connection
		if line == 'quit':
			self.log.info("Client asked us to close the Connection")
			return False

		# process the received line
		success, msg = self.processLine(line)

		# success = False -> error 
		if success == False:
			# on error-responses the message is mandatory
			if msg is None:
				msg = '<no message>'

			# respond with 'error' and the message
			filelike.write('error '+msg+'\n')
			self.log.info("Function-Call returned an Error: %s", msg)

			# keep on listening on that connection
			return True

		# success = True and not message
		if msg is None:
			# respond with a simple 'ok'
			filelike.write('ok\n')
		else:
			# respond with the returned message
			filelike.write('ok '+msg+'\n')
		return True

	def processLine(self, line):
		# split line into command and optional args
		command, argstring = (line+' ').split(' ', 1)
		args = argstring.strip().split()

		# log function-call as parsed
		self.log.info("Read Function-Call from Socket: %s( %s )", command, args)

		# check that the function-call is a known Command
		if not hasattr(self.commands, command):
			return False, 'unknown command %s' % command


		try:
			# fetch the function-pointer
			f = getattr(self.commands, command)

			# call the function
			ret = f(*args)

			# if it returned an iterable, probably (Success, Message), pass that on
			if hasattr(ret, '__iter__'):
				return ret
			else:
				# otherwise construct a tuple
				return (ret, None)

		except Exception as e:
			self.log.error("Trapped Exception in Remote-Communication: %s", e)
			traceback.print_exc()

			# In case of an Exception, return that
			return False, str(e)