import tornado
import tornado.web
import tornado.gen
from tornado.ioloop import IOLoop

from constants import argumentTypes
from objects import glob

# Copied from https://zxq.co/ripple/ripple-python-common/src/branch/master/web/requestsManager.py
class asyncRequestHandler(tornado.web.RequestHandler):
	"""
	Tornado asynchronous request handler
	create a class that extends this one (requestHelper.asyncRequestHandler)
	use asyncGet() and asyncPost() instead of get() and post().
	Done. I'm not kidding.
	"""
	@tornado.web.asynchronous
	@tornado.gen.engine
	def get(self, *args, **kwargs):
		try:
			yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs)))
		finally:
			if not self._finished:
				self.finish()

	@tornado.web.asynchronous
	@tornado.gen.engine
	def post(self, *args, **kwargs):
		try:
			yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs)))
		finally:
			if not self._finished:
				self.finish()

	def asyncGet(self, *args, **kwargs):
		self.send_error(405)

	def asyncPost(self, *args, **kwargs):
		self.send_error(405)

	def getRequestIP(self):
		"""
		Return CF-Connecting-IP (request IP when under cloudflare, you have to configure nginx to enable that)
		If that fails, return X-Forwarded-For (request IP when not under Cloudflare)
		if everything else fails, return remote IP

		:return: Client IP address
		"""
		if "CF-Connecting-IP" in self.request.headers:
			return self.request.headers.get("CF-Connecting-IP")
		elif "X-Forwarded-For" in self.request.headers:
			return self.request.headers.get("X-Forwarded-For")
		else:
			return self.request.remote_ip


def runBackground(data, callback):
	"""
	Run a function in the background.
	Used to handle multiple requests at the same time

	:param data: (func, args, kwargs)
	:param callback: function to call when `func` (data[0]) returns
	:return:
	"""
	func, args, kwargs = data
	def _callback(result):
		IOLoop.instance().add_callback(lambda: callback(result))
	glob.pool.apply_async(func, args, kwargs, _callback)

def check_arguments(arguments, arguments_filter):
	filter_pass = []
	for k, v in arguments_filter.items():
		if v == argumentTypes.optional:
			filter_pass.append( arg_filter_and(arguments, k) )
		elif v == argumentTypes.required:
			filter_pass.append( arg_filter_require_all(arguments, k) )
		elif v == argumentTypes.one_required:
			filter_pass.append( arg_filter_first(arguments, k, False) )
		elif v == argumentTypes.only_one:
			filter_pass.append( arg_filter_only_one(arguments, k) )
	return filter_pass

def arg_filter_and(arguments, filter, can_false = False):
	arg_filter = []
	for i in filter:
		if i in arguments:
			arg_filter.append(i)
	if can_false:
		return arg_filter if len(arg_filter) else False
	return arg_filter

def arg_filter_require_all(arguments, required):
	for i in required:
		if i not in arguments:
			return False
	return required

def arg_filter_only_one(arguments, required):
	arg_filter = []
	for i in required:
		if i in arguments:
			arg_filter.append(i)
	return True if len(arg_filter) == 1 else False

def arg_filter_first(arguments, filter, optional = True):
	for i in filter:
		if i in arguments:
			return i
	return optional
