# -*- Mode: Python; tab-width: 4 -*-

import corosock
import coroutine
import regex
import socket
import string

class counter:
	def __init__ (self, value=0):
		self.value = value
	def __int__ (self):
		return self.value
	def increment (self):
		self.value = self.value + 1
		return self.value
	def decrement (self):
		self.value = self.value - 1
		return self.value

class http_client:

	def __init__ (self, conn, addr):
		self.s = corosock.coroutine_socket (conn)
		self.addr = addr
		self.buffer = ''
		self.lines = []
			
	def read_line (self):
		# could be yet another use for coroutines
		if self.lines:
			l = self.lines[0]
			self.lines = self.lines[1:]
			return l
		else:
			while not self.lines:
				buffer = self.s.recv (8192)
				lines = string.split (buffer, '\r\n')
				for l in lines[:-1]:
					self.lines.append (l)
				self.buffer = lines[-1]
			return self.read_line()

	def read_header (self):
		header = []
		while 1:
			l = self.read_line()
			if not l:
				break
			else:
				header.append (l)
		self.header = header
		return header

	def send (self, data):
		olb = lb = len(data)
		while lb:
			ns = self.s.send (data)
			lb = lb - ns
		return olb

	# --------------------------------------------------
	# split a uri
	# --------------------------------------------------

	# <path>;<params>?<query>#<fragment>
	path_regex = regex.compile (
	#        path        params        query       fragment
		'\\([^;?#]*\\)\\(;[^?#]*\\)?\\(\\?[^#]*\)?\(#.*\)?'
		)

	def split_uri (self, uri):
		if self.path_regex.match (uri) != len(uri):
			raise ValueError, "Broken URI"
		else:
			return map (lambda i,r=self.path_regex: r.group(i), range(1,5))

	def unpack_get_vars (self, query):
		vars = {}
		for var in string.split (query[1:], '&'):
			name, value = string.split (var, '=')
			vars[name] = value
		return vars

	session_counter = counter()
	sessions = {}

	def handle_request (self):
		request = self.header[0]
		method, uri, version = string.split (request)
		
		[path, params, query, fragment] = self.split_uri (uri)

		parts = string.split (path, '/')

		header = (
			'HTTP/1.0 200 OK\r\n'
			'Content-Type: text/html\r\n'
			'Connection: close\r\n'
			'\r\n'
			)

		if query:
			self.s.send (header)
			vars = self.unpack_get_vars (query)
			if vars.has_key ('login'):
				# create a new session
				name = vars['login']
				sid = self.session_counter.increment()
				c = coroutine.new (session_loop, (name, sid, self))
				corosock.schedule (c)
				self.sessions[sid] = c
			elif vars.has_key ('session'):
				sid = int(vars['session'])
				print 'existing session', self.sessions[sid]
				self.vars = vars
				# resume an existing session
				corosock.schedule (self.sessions[sid], self)
			else:
				self.s.send ('<html><h1>strange</h1>query=%s</html>' % repr(query))
				self.s.close()
		else:
			# present login
			self.s.send (header)
			self.send (
				'<html>\r\n'
				'<title>Stateful HTTP Server</title>\r\n'
				'<center>\r\n'
				'<h2>Hello</h2><form method=GET action="/blackjack">\r\n'
				'<input name=login size=50 maxlength=80 value="">\r\n'
				'<input type=submit value="Login">\r\n'
				'</form>\r\n'
				)
			# send data
			self.s.close()
		
def session_loop (name, sid, client):
	try:
		import blackjack
		game = blackjack.game()
		# deal first two cards
		game.next (force=1)
		result = game.next (force=1)
		game_counter = counter()
		client.s.send (
			'<html><h1>Welcome To The Machine, %s</h1>\r\n' % name +
			'  <form method=get action="/blackjack">\r\n'
			'    <input type=hidden name=session value=%d>\r\n' % sid +
			'    <input type=submit name="hit" value="Hit Me">\r\n'
			'    <input type=submit name="stand" value="Stand">\r\n'
			'  </form>\r\n'
			'  <h3>dealer: %s</h3>\r\n' % (repr(game.cards(game.dhand))) + 
			'  <h3>player: %s</h3>\r\n' % (repr(game.cards(game.phand))) + 
			((result and '  <h3>result: %s</h3>\r\n' % repr(result)) or '') +
			'  <h3>winnings: %s<h3>\r\n' % (game.winnings) +
			'</html>\r\n'
			)
		client.s.close()
		while 1:
			# fetch the next hit [if there ever is one]
			client = coroutine.main (None)
			if client.vars.has_key ('deal'):
				game_counter.increment()
				game.next (force=1)
				result = game.next (force=1)
			elif client.vars.has_key ('hit'):
				result = game.next (1)
			else:
				result = game.next (0)
			if result is not None:
				[phand, dhand] = game.last_hand
			else:
				phand, dhand = game.phand, game.dhand
			client.s.send (
				'<html><h1>Game #%d</h1></html>' % game_counter +
				'  <h2>Player: %s</h2>\r\n' % name
				)
			if result is None:
				client.s.send (
					'  <form method=get action="/blackjack">\r\n'
					'    <input type=hidden name=session value=%d>\r\n' % sid +
					'    <input type=submit name="hit" value="Hit Me">\r\n'
					'    <input type=submit name="stand" value="Stand">\r\n'
					'  </form>\r\n'
					)
			else:
				client.s.send (
					'  <form method=get action="/blackjack">\r\n'
					'    <input type=hidden name=session value=%d>\r\n' % sid +
					'    <input type=submit name="deal" value="Deal">\r\n'
					'  </from>\r\n'
					)
			client.send (
				'  <h3>dealer: %s</h3>\r\n' % (repr(game.cards(dhand))) + 
				'  <h3>player: %s</h3>\r\n' % (repr(game.cards(phand))) +
				((result and '  <h3>result: %s</h3>\r\n' % repr(result)) or '') +
				'  <h3>winnings: %s<h3>\r\n' % (game.winnings) +
				'</html>\r\n'
				)
			client.s.close()
	except:
		import traceback
		traceback.print_exc()

def spawn_client (conn, addr):
	try:
		print 'spawn_client (%s)' % repr(addr)
		h = http_client (conn, addr)
		h.read_header()
		h.handle_request()
		coroutine.main (None)
	except:
		import traceback
		traceback.print_exc()

def serve():
	try:
		s = corosock.coroutine_socket()
		s.create_socket (socket.AF_INET, socket.SOCK_STREAM)
		s.set_reuse_addr()
		s.bind (('', 7777))
		s.listen (128)
		while 1:
			print 'accept loop, just before accept()'
			conn, addr = s.accept()
			print 'incoming connection from', addr
			c = coroutine.new (spawn_client, (conn, addr))
			corosock.schedule (c, None)
	except:
		import traceback
		traceback.print_exc()
		
if __name__ == '__main__':
	try:
		c = coroutine.new (serve, ())
		corosock.schedule (c)
		corosock.event_loop (30.0)
	except:
		import traceback
		traceback.print_exc()
