""" ExternalConfigServer is a replacement for WSGITileServer that uses external
    configuration fetched via HTTP to service all config requests.
    
    Example usage, with gunicorn (http://gunicorn.org):
      
      gunicorn --bind localhost:8888 "TileStache.Goodies.ExternalConfigServer:WSGIServer(url)"
"""

from urllib import urlopen
import logging

try:
	from json import load as json_load
except ImportError:
	from simplejson import load as json_load

import TileStache

class DynamicLayers:
	
	def __init__(self, config, url_root, cache_responses, dirpath):
		self.config = config
		self.url_root = url_root
		self.dirpath = dirpath
		self.cache_responses = cache_responses;
		self.seen_layers = {}
		self.lookup_failures = set()
	
	def keys(self):
		return self.seen_layers.keys()
	
	def items(self):
		return self.seen_layers.items()

	def parse_layer(self, layer_json):
		layer_dict = json_load(layer_json)
		return TileStache.Config._parseConfigfileLayer(layer_dict, self.config, self.dirpath)
	
	def __contains__(self, key):
		# If caching is enabled and we've seen a request for this layer before, return True unless
		# the prior lookup failed to find this layer.
		if self.cache_responses:
			if key in self.seen_layers:
				return True
			elif key in self.lookup_failures:
				return False
		
		res = urlopen(self.url_root + "/layer/" + key)
		
		if self.cache_responses:
			if res.getcode() != 200:
				# Cache a failed lookup
				self.lookup_failures.add(key)
			else :
				# If lookup succeeded and we are caching, parse the layer now so that a subsequent
				# call to __getitem__ doesn't require a call to the config server.  If we aren't
				# caching, we skip this step to avoid an unnecessary json parse.
				try:
					self.seen_layers[key] = self.parse_layer(res)
				except ValueError:
					# The JSON received by the config server was invalid.  Treat this layer as a
					# failure.  We don't want to raise ValueError from here because other parts
					# of TileStache are just expecting a boolean response from __contains__
					logging.error("Invalid JSON response seen for %s", key)
					self.lookup_failures.add(key)
					return False

		if res.getcode() != 200:
			logging.info("Config response code %s for %s", res.getcode(), key)		
		return res.getcode() == 200
	
	def __getitem__(self, key):
		if self.cache_responses:
			if key in self.seen_layers:
				return self.seen_layers[key]
			elif key in self.lookup_failures:
				# If we are caching, raise KnownUnknown if we have previously failed to find this layer
				raise TileStache.KnownUnknown("Layer %s previously not found", key)
		
		logging.debug("Requesting layer %s", self.url_root + "/layer/" + key)
		res = urlopen(self.url_root + "/layer/" + key)
		if (res.getcode() != 200) :
			logging.info("Config response code %s for %s", res.getcode(), key)
			if (self.cache_responses) :
				self.lookup_failures.add(key)
			raise TileStache.KnownUnknown("Layer %s not found", key)
		
		try :
			layer = self.parse_layer(res)
			self.seen_layers[key] = layer
			return layer
		except ValueError:
			logging.error("Invalid JSON response seen for %s", key)
			if (self.cache_responses) :
				# If caching responses, cache this failure
				self.lookup_failures.add(key)
			# KnownUnknown seems like the appropriate thing to raise here since this is akin
			# to a missing configuration.
			raise TileStache.KnownUnknown("Failed to parse JSON configuration for %s", key)

class ExternalConfiguration:
	
	def __init__(self, url_root, cache_dict, cache_responses, dirpath):
		self.cache = TileStache.Config._parseConfigfileCache(cache_dict, dirpath)
		self.dirpath = dirpath
		self.layers = DynamicLayers(self, url_root, cache_responses, dirpath)

class WSGIServer (TileStache.WSGITileServer):
	
	"""
		Wrap WSGI application, passing it a custom configuration.
		
		The WSGI application is an instance of TileStache:WSGITileServer.
		
		This method is initiated with a url_root that contains the scheme, host, port
		and path that must prefix the API calls on our local server.  Any valid http
		or https urls should work.
		
		The cache_responses parameter tells TileStache to cache all responses from
		the configuration server.
	"""
	
	def __init__(self, url_root, cache_responses=True, debug_level="DEBUG"):
		logging.basicConfig(level=debug_level)
		
		# Call API server at url to grab cache_dict
		cache_dict = json_load(urlopen(url_root + "/cache"))
		
		dirpath = '/tmp/stache'
		
		config = ExternalConfiguration(url_root, cache_dict, cache_responses, dirpath)
		
		TileStache.WSGITileServer.__init__(self, config, False)
	
	def __call__(self, environ, start_response):
		response = TileStache.WSGITileServer.__call__(self, environ, start_response)
		return response
