"""Component-based web application framework

TODO:
   * http://java.sun.com/products/servlet/2.3/javadoc/index.html
   * make more Servlet like, javaCap all the method names
   * Implement ServletRequest/Response as well as
       HttpServletRequest/Response

Copyright (C) 2002 l.m.orchard <deus_x@pobox.com>
http://www.decafbad.com

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 2
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, published at 
http://www.gnu.org/copyleft/gpl.html
"""

import os, sys, string, re
import cgi
import Cookie
import xmlrpclib
import ConfigParser

#from types import *
from Cheetah.Template import Template
from xml.sax import saxutils

# {{{ class RequestContext

class RequestContext:
	"""Encapsulated web request, contains parameters and CGI-style variables

	"""

	def __init__(self):
		self.__form = cgi.FieldStorage()
		self.__cookies = Cookie.Cookie()
		self.__env = os.environ

# 	def __getattr__(self, name):
# 		if name[0] == '_':
# 			raise AttributeError
# 		try:
# 			value = self.__form[name].value
# 		except (TypeError, KeyError):
# 			value = ''
# 		else:
# 			value = string.strip(value)
# 		setattr(self, name, value)
# 		return value

# 	def __getitem__(self, key):
# 		return getattr(self, key)

	def is_ssl(self):
		return os.environ.get('SSL_PROTOCOL', '') != ''

	def get_cookie(self, name):
		return self.__cookies[name].value

	def get_env_parameter(self, name, default=""):
		### TODO: make more like / merge with __getattr__
		return self.__env[name] #.getvalue(name, default)

	def get_parameter(self, name, default=""):
		### TODO: make more like / merge with __getattr__
		return self.__form.getvalue(name, default)

	def get_parameter_names(self):
		return self.__form.keys()

	def get_parameter_values(self):
		return self.__form.values()

	def get_method(self):
		return os.environ.get('REQUEST_METHOD', '')

	def get_path_info(self):
		return os.environ.get('PATH_INFO', '')

	def get_path_translated(self):
		return os.environ.get('PATH_TRANSLATED', '')

	def get_query_string(self):
		return os.environ.get('QUERY_STRING', '')

	def get_remote_user(self):
		return os.environ.get('REMOTE_USER', '')

	def get_script_name(self):
		return os.environ.get('SCRIPT_NAME', '')

	def get_script_path(self):
		return os.environ.get('SCRIPT_PATH', '')

	def get_server_host(self):
		return os.environ.get('HTTP_HOST')

	def escape(self, s):
		s = string.replace(s, '&', '&amp;')
		s = string.replace(s, '<', '&lt;')
		s = string.replace(s, '>', '&gt;')
		return s

	def escapeq(self, s):
		s = escape(s)
		s = string.replace(s, '"', '&quot;')
		return s

	def get_qualified_url(self, uri = None):
		schema, stdport = (('http', '80'), ('https', '443'))[self.is_ssl()]
		host = os.environ.get('HTTP_HOST')
		if not host:
			host = os.environ.get('SERVER_NAME')
			port = os.environ.get('SERVER_PORT', '80')
			if port != stdport: host = host + ":" + port
		result = "%s://%s" % (schema, host)
		if uri: result = result + uri

		return result

	def get_base_url(self):
		return self.get_qualified_url(self.get_script_name())

# }}}
# {{{ class ResponseContext

class ResponseContext:
	"""Encapsulates the response to a web request, uses the template engine

	"""

	def __init__(self):		
		self.status  = "200 OK"
		self.content_type = "text/html"
		self.error   = 0
		self.headers = []
		self.ns      = {}
		self.cookies = Cookie.Cookie()
		self.searchlist = []
		self.template = "default.tmpl"
		self.template_path = "templates"

		self.sent_headers  = 0
		self.sent_response = 0
		self.redirect_url  = ""

		self.line_buf = []

	def set_status(self, val):
		self.status = val
		self.error  = 0

	def set_content_type(self, val):
		self.content_type = val

	def add_header(self, val):
		self.headers.append(val)
		
	def add_cookie(self, name, val):
		self.cookies[name] = val
	
	def set_error(self, val):
		self.status = val
		self.error  = 1

	def add_namespace(self, val):
		self.searchlist.append(val)

	def set_template(self, val):
		self.template = val

	def write(self, str):
		self.line_buf.append(str)

	def send_headers(self):
		if self.sent_headers:
			return

		print "Status: %s" % self.status
		print "Content-type: %s" % self.content_type
		if len(self.cookies) > 0:
			print self.cookies
		if len(self.headers) > 0:
			print self.headers
		print
     
		self.sent_headers = 1

	def send_redirect(self, loc):
		self.set_status("302 Moved")
		#self.add_header("Location: %s" % loc)
		self.headers = ["Location: %s" % loc]
		self.send_headers()

	def send_response(self):
		self.send_headers()
		if len(self.line_buf) > 0:
			print string.join(self.line_buf, "\n")
		else:
			print Template(file=os.path.join(self.template_path, self.template),
						   searchList=self.searchlist)

	def print_test(self):
		self.send_headers()
		cgi.print_environ()
		cgi.print_environ_usage()

# }}}
# {{{ class ApplicationComponent

class ApplicationComponent:
	"""Basic web application component

	"""

	def __init__(self, conf_fn):
		self.conf_fn = conf_fn
		self.config = ConfigParser.ConfigParser()
		self.config.read(conf_fn)
		self.app_context = None

	def set_application_context(self, ctx):
		self.app_context = ctx

	def get_application_context(self):
		return self.app_context

	def get_config(self, key, section='global', default=None):
		app = self.get_application_context()

		if self.config.has_option(section, key):
			return self.config.get(section, key)
		elif self.config.has_option('global', key):
			return self.config.get('global', key)
		else:
			return app.get_config(key, section, default)

# }}}
# {{{ class ApplicationContext

class ApplicationContext:
	"""Container for web application components, routes web requests

	"""

	def __init__(self, conf_fn):
		self.config = ConfigParser.ConfigParser()
		self.config.read(conf_fn)

		self.config_path = self.get_config('config_path', 'global', 'conf')
		self.default_component = self.get_config('default_component')
		self.default_method = self.get_config('default_method')
		self.xmlrpc_path = self.get_config('xmlrpc_path', 'global', '/RPC2')

		self.components = {}
		self.load_components()

	def load_components(self):
		for comp_name in self.config.options('components'):
			comp_module = self.config.get('components', comp_name)
			exec "import %s" % comp_module
			comp = eval("%s.%s('%s')" %
						(comp_module, comp_module,
						 os.path.join(self.config_path, "%s.conf" % comp_name)))
			self.add_component(comp_name, comp)
	
	def get_config(self, key, section='global', default=None):
		if self.config.has_option(section, key):
			return self.config.get(section, key)
		elif self.config.has_option('global', key):
			return self.config.get('global', key)
		else:
			return default

	def set_default_component(self, name):
		self.default_component = name

	def set_default_method(self, name):
		self.default_method = name

	def add_component(self, name, comp):
		self.components[name] = comp
		comp.set_application_context(self)

	def serve_request(self):

		if os.environ.has_key('PATH_INFO'):
			path_info = os.environ.get('PATH_INFO','')
			if path_info[0-len(self.xmlrpc_path):] == self.xmlrpc_path:

				xml_in = sys.stdin.read(int(os.environ.get('CONTENT_LENGTH', '')))

				params, method = xmlrpclib.loads(xml_in)
				component_name, method_name = string.split(method, ".")

				if self.components.has_key(component_name):
					component = self.components[component_name]
				elif self.components.has_key(self.default_component):
					component = self.components[self.default_component]
				else:
					xml_out = xmlrpclib.dumps(xmlrpclib.Fault(1, "no such component"))

				comp_method_name = "xmlrpc_%s" % method_name
				try:
					method = getattr(component, comp_method_name)
				except AttributeError:
					xml_out = xmlrpclib.dumps(xmlrpclib.Fault(1, "no such method"))
				else:
					result = method(params)
					xml_out = xmlrpclib.dumps((result,), methodresponse=1)

				print "Content-type: text/xml"
				print
				print xml_out						 
			
				return
		
		req = RequestContext()
		res = ResponseContext()

		### Try to get the component
		component_name = req.get_parameter('comp')
		if self.components.has_key(component_name):
			component = self.components[component_name]
		elif self.components.has_key(self.default_component):
			component = self.components[self.default_component]
		else:
			### Need error handling
			pass
			
		### Try to get the method
		method_name    = "web_%s" % req.get_parameter('method')
		try:
			method = getattr(component, method_name)
		except AttributeError:
			try:
				method = getattr(component, "web_%s" % self.default_method)
			except AttributeError:
				### Need error checking
				raise

		app_ns = { 'application' : self,
				   'request'     : req,
				   'response'    : res }
		res.add_namespace(app_ns)
		
		### Run the app component method
		method(self, req, res)

		### Finish by sending the response
		res.send_response()

# }}}
