#!/usr/bin/env python

from twisted.internet import reactor, protocol
from twisted.web import http
from twisted.web import server
from twisted.spread import pb
from twisted.web.resource import Resource
from twisted.python import log
from twisted.web.client import FileBodyProducer
from os import path
import os
import subprocess

class Script(Resource):
	isLeaf = True

	def find_suitable_script(self, patharray):
		for i in reversed(range(len(patharray) + 1)):
			potential_path_array = ['scripts'] + patharray[:i]
			remainder_array = patharray[i:]
			potential_path = apply(path.join, potential_path_array)

			if not path.exists(potential_path):
				continue

			if path.isfile(potential_path):
				return (potential_path,remainder_array)

			if path.isdir(potential_path):
				main_path = apply(path.join, potential_path_array + ['main'])

				if not path.exists(main_path):
					continue

				if path.isfile(main_path):
					return (main_path,remainder_array)

		raise BaseException("Unknown script: " + apply(path.join, patharray))

	def render(self, request):
		print "Request from '%s'" % request.getClientIP()
		print request.method
		print request.postpath

		wrapper_script = path.join(".","script_wrapper")
		(the_script,the_args) = self.find_suitable_script(request.postpath)
		content_type = request.getHeader("Content-Type") or "text/plain"
		requesting_user = request.getHeader("Imp-Requesting-User")

		print "Request done by user '%s'" % requesting_user

		env = os.environ.copy()
		if not requesting_user is None:
			env["IMP_REQUESTING_USER"] = requesting_user
		
		p = ScriptProtocol(request)
		request.setHeader('Access-Control-Allow-Origin', '*')
		request.setHeader('Access-Control-Allow-Methods', 'GET, NEXT, PREVIOUS, PAUSE, PUT, ENQUEUE, POST, DELETE')
		request.setHeader('Access-Control-Allow-Headers', 'x-prototype-version,x-requested-with, Content-Type')
		request.setHeader('Access-Control-Max-Age', 2520)		
		request.content = p
		reactor.spawnProcess(p, 
							wrapper_script,
							[wrapper_script,the_script,content_type,request.method] + the_args,
							env)
		return server.NOT_DONE_YET

"""
Ugly hacks galore here :(
"""
class FakeSeeker():
	def seek(self,ignore,ignore2):
		pass

	def write(self,ignore):
		pass

	def close(self):
		pass

"""
Normal requests wait until everything is received before rendering.
This one only waits until it received the headers, and expects the resource
to set the content destination.

This is sort of a hack, because I'm overwriting default behaviour,
but it is semantically sound mostly.

TODO: Possibly a problem with buffer overflow, in that case use
	  an intermediary file after all, but start reading immediately.
"""
class LiveRequest(server.Request):
	"""
	I want the resource to determine the content, so I'm loading a dummy
	object instead of a file/stringIO object.
	"""
	def gotLength(self, length):
		self.content = FakeSeeker()


"""
A channel for LiveRequests
"""
class LiveHTTPChannel(http.HTTPChannel):
	"""
	This channel receives the request once the headers are received.
	"""
	def allHeadersReceived(self):
		http.HTTPChannel.allHeadersReceived(self)
		self.requests[-1].requestReceived(self._command, self._path, self._version)

	"""
	Close the content when done
	"""
	def allContentReceived(self):
		command = self._command
		path = self._path
		version = self._version

		# reset ALL state variables, so we don't interfere with next request
		self.length = 0
		self._receivedHeaderCount = 0
		self._HTTPChannel__first_line = 1
		self._transferDecoder = None
		del self._command, self._path, self._version

		# Disable the idle timeout, in case this request takes a long
		# time to finish generating output.
		if self.timeOut:
			self._savedTimeOut = self.setTimeout(None)

		self.requests[-1].content.close()

"""
A Site subclass which initializes the Site to use
LiveRequests and LiveHTTPChannels
"""
class LiveSite(server.Site):
	requestFactory = LiveRequest
	protocol = LiveHTTPChannel



class ScriptProtocol(protocol.ProcessProtocol, pb.Viewable):
	got_content_type = False
	content_type = ''
	errortext = ''
	requestFinished = False

	def view_resumeProducing(self, issuer):
		self.resumeProducing()

	def view_pauseProducing(self, issuer):
		self.pauseProducing()

	def view_stopProducing(self, issuer):
		self.stopProducing()

	def resumeProducing(self):
		self.transport.resumeProducing()

	def pauseProducing(self):
		self.transport.pauseProducing()

	def stopProducing(self):
		print "closing output"
		self.transport.closeStdout()
		self.transport.closeStderr()

	def __init__(self, request):
		self.request = request
		self.request.notifyFinish().addBoth(self.onRequestFinished)

	def connectionMade(self):
		print "connection established"
		self.request.registerProducer(self, 1)

	def seek(self, ignore, ignore2):
		pass
	
	def write(self,data):
		self.transport.write(data)

	def close(self):
		print "closing input"
		self.transport.closeStdin()

	def errReceived(self, error):
		self.errortext = self.errortext + error

	def outReceived(self, output):
		if not self.got_content_type:
			newline_loc = output.find('\n')
			if newline_loc == -1:
				self.content_type += output
			else:
				self.content_type += output[:newline_loc]
				self.request.setHeader("Content-Type",self.content_type)
				self.got_content_type = True
				self.request.write(output[newline_loc+1:])
		else:
			self.request.write(output)

	def onRequestFinished(self,ignore):
		self.requestFinished = True

	def processEnded(self, reason):
		if reason.value.exitCode != 0:
			log.err("Script %s exited with exit code %s" % (self.request.uri, reason.value.exitCode))
		if self.errortext:
			log.err("Errors from script %s: %s" % (self.request.uri, self.errortext))
		self.request.unregisterProducer()
		if not self.requestFinished:
			self.request.finish()


root = Script()
factory = LiveSite(root)
reactor.listenTCP(8085, factory)
reactor.run()
