import http.cookies
import urllib.parse

from Ozark.Injector import InjectorFactory, Injector

class RequestFields:
	def __init__(self, pathFields, formFields):
		self.PathFields = list(pathFields)
		self.FormFields = list(formFields)

	def PopValues(self, fieldName):
		for name, value in self.PathFields[:]:
			if name == fieldName:
				self.PathFields.remove((name, value))
				yield value
				# Path fields cannot be variadic and cannot be overridden by form fields.
				return

		for name, value in self.FormFields[:]:
			if name == fieldName:
				self.FormFields.remove((name, value))
				yield value

	@property
	def Fields(self):
		return set(k for k, v in self.PathFields) | set(k for k, v in self.FormFields)

class Request:
	def __init__(self, *, environ, injector: Injector):
		self.Method = environ['REQUEST_METHOD']
		self.Path = environ.get('PATH_INFO', '/')
		if self.Method == 'GET':
			formData = environ.get('QUERY_STRING', '')
		else:
			formData = environ['wsgi.input'].read()
		if isinstance(formData, bytes):
			formData = formData.decode('utf-8')
		self.Form = urllib.parse.parse_qsl(formData)

		self._Injector = injector
		self._Injector['request'] = self
		self._Injector['cookieJar'] = http.cookies.SimpleCookie(environ.get('HTTP_COOKIE', ''))

	def Dispatch(self, route):
		requestFields = RequestFields(route.GetPathFields(self), self.Form)

		self._Injector['requestTarget'] = route.Target
		self._Injector['requestFields'] = requestFields
		# TODO: make this more clean
		try:
			responseBody = self._Injector(route.Target)
		finally:
			if 'mapper' in self._Injector.Cache:
				# Release all locks
				self._Injector.Cache['mapper'].Rollback()

		if len(requestFields.Fields) > 0:
			raise TypeError("unknown argument '{}'".format(requestFields.Fields.pop()))

		return responseBody

class RequestFactory:
	def __init__(self, *, requestInjectorFactory: InjectorFactory):
		self._RequestInjectorFactory = requestInjectorFactory

	def __call__(self, environ):
		# FIXME: unpack environ here
		requestInjector = self._RequestInjectorFactory()
		return Request(
			environ = environ,
			injector = requestInjector)