
# Invivo AI challenge simulation environment
# Copyright 2009 Simon Funk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""This quick hack provides a pseudo-Controller for Game01 which
bridges the Interface over a TCP connection to a Brain running
on another process.

See source of run.remote for usage example.

The Interface state is transacted via JSON encoded values, in
blocks delimited by \n's, to make it easy for the client on
the other side to be in any language of choice.

Pass the Server class as the Controller to Game01 to run the
simulation in server mode.  That is:

  run -c Brain01_Net.Server

(.Server is optional since .Controller is aliased to .Server
at the bottom of this file.)

The Client class below can be used as a wrapper around an existing
python Game01-compatable Brain, or simply as a template for a client
in some other language.

Presently everything runs in blocking mode, so no UI event processing
will happen between frames or while the client process is paused.
A sufficiently slow Client may cause the server to crap out due to
unprocessed event queue overflow.
"""


import socket
from Interface01 import Interface01

class NetPipe(object):
	"""Just a mix-in for a few common methods of classes managing a single data pipe."""

	def __init__(self):
		self.inbuf = ''	# Input buffer (leftover received data not used by last call to readblock)

	def sendblock(self, block):
		"Sends a block of data down our pipe.  The block may not contain \n's."
		self.pipe.sendall(block+"\n")

	def readblock(self):
		"Reads a block of data sent by sendblock."
		# Not very efficient, but simple:
		while '\n' not in self.inbuf:
			buf = self.pipe.recv(2048)
			if not buf:
				raise Exception("Broken pipe?")
			self.inbuf += buf
		pos = self.inbuf.find('\n')
		block = self.inbuf[:pos]
		self.inbuf = self.inbuf[pos+1:]
		return block


class Server(NetPipe):

	"""This "Controller" for Game01 just waits for a connection on
	a socket from a Client class (below).
	"""
	def __init__(self, interfaces, port=7832):
		"""Interfaces is a pair of Interface01 objects."""
		NetPipe.__init__(self)

		self.interfaces = interfaces

		self.listener = socket.socket()
		self.listener.bind(('', port))
		self.listener.listen(1)	# For now just allow one connection...

		print "Listening for a connection to port %d."%(port,)

		if False:
			#
			# Should move to Io and non-blocking operation, but there's much more to do for that...
			#
			Io.addFileHandler(self.listener, Io.read, self.handleConnection)
		else:
			#
			# Meantime, just block waiting for the connection:
			#
			self.handleConnection(self.listener)

	def handleConnection(self, listener):
		"Called by Io when a new connection is made."

		if self.listener is None:
			print "WARNING: Recieved incoming connection attempt when already connected."
			return

		try:
			sock, addr = listener.accept()
		except Exception, e:
			print "Accept failed: %s"(e,)
			return

		print "Connected by %s"%(addr,)

		# For now, only accept one incoming connection (shut down the port):
		self.listener.close()
		self.listener = None

		self.pipe = sock

		#
		# Hmm...  Actually I need to rework the main loop in a big way...
		# (Need to change the calling hierarchy so that the Brain calls
		#  update when it is done.  Right now as things are, the system
		#  must lock up until the brain is ready...)
		#
		self.pipe.setblocking(1)
		for interface in self.interfaces:
			self.sendblock(interface.staticToJson())

	def update(self, time):

		self.sendblock("%s"%(time,))

		for interface in self.interfaces:
			self.sendblock(interface.sensorsToJson())

		for interface in self.interfaces:
			interface.controlsFromJson(self.readblock())

class Client(NetPipe):

	"""This is a client which takes a Game01 Controller and connects
	it to a remote game Server (above).
	"""
	def __init__(self, controller, host='', port=7832):
		"controller is a Game01 Controller (e.g., Brain01_Test.Controller)."
		NetPipe.__init__(self)

		self.pipe = socket.socket()

		print "Connecting to %s:%s"%(host, port)

		self.pipe.connect((host, port))

		self.interfaces = [Interface01(), Interface01()]

		for interface in self.interfaces:
			interface.staticFromJson(self.readblock())

		self.controller = controller(self.interfaces)

		self.run()

	def update(self):

		time = float(self.readblock())

		for interface in self.interfaces:
			interface.sensorsFromJson(self.readblock())

		self.controller.update(time)

		for interface in self.interfaces:
			self.sendblock(interface.controlsToJson())

	def run(self):

		while True:
			self.update()

#
# Server is a bonified Controller.  Make it the default Controller for this file:
#
Controller = Server

