# The primary purpose of this module is to run data hashing and comparison functions
# It is also called during the intialization of modules to register their hashing and comparison functions.
# Try to leave all the boilerplate calls like DB access and JSON decoding in the main.py file

import hijackingprevention.session as session
import hijackingprevention.api_user as api_user

from tornado import gen

import logging
logger = logging.getLogger(__name__)

OK = (200, 'OK')

class Receiver():
	"""This class handles all requests to hash and interpret data.
	Most of the shared functionalty that underpins the API is defined here.
	Functions in here fall into two catagories:
		Functions that allow modules to register hashers or comparers.
		Functions that run the registered hashers or comparers.
	Please note:
		If a function you are considering including in this file does not invoke a hasher, translator, or comparer it may not belong in this file.
	"""
	def __init__(self):
		self.__hashers = {}
		self.__translators = {}
		self.__comparers = {}
		self.__maxscores = {}
		self.__callbacks = {}

	# Module registration functions

	def add_hasher(self, name, fxn):
		"""This is called by each module on startup to register its sitewide hasher."""
		self.__hashers[name] = fxn

	def add_translator(self, name, fxn):
		"""This is called by each module on startup to register its data translator.
		Translators hash data, which has already been hashed by a (sitewide) hasher, with a per user salt.
		"""
		self.__translators[name] = fxn

	def add_comparer(self, name, fxn, score):
		"""This is called by each module on startup to register its hash comparer."""
		self.__comparers[name] = fxn
		self.__maxscores[name] = score

	# Module users and API interface

	@gen.coroutine
	def add_data(self, req, headers, db):
		"""This function is called when data is received from a browser to hash and store the data"""
		# This function has, rather irritatingly, remained in spite of my attempt to keep database access out of the api.py file.  
		if req['name'] in self.__hashers.keys():
			#setup to invoke hasher
			site = api_user.Site(db)
			yield site.get_by_client_key(req['ck'],
				headers.get("Host"))
			salt = site.get_salt(req['name'])
			#invoke hasher
			hash = yield self.__hashers[req['name']](req['data'],
				headers, salt)
			#store the result
			site_id = site.get_id()
			ses = session.Session(req['sid'], site_id, db) #setup session object
			yield ses.read_db() #read session if it exists
			ses.add_data({req['name']:hash}) #add data to session object
			yield ses.write_out() #update session object in database
			return(OK) #Nothing failed. Right? oops.
		else:
			logger.warning('Could not find a handler for "' + req['name'] + '"')
			return(400, 'Err: Could not find a handler for "' + req['name'] + '"')

	@gen.coroutine
	def copy_data(self, ses, member):
		"""This function is called to store session data permenantly to the user profile"""
		data = {}
		for dt in ses.keys():
			data[dt] = yield self.__translators[dt](ses[dt])
		member.add_data(data)
		return(OK)

	@gen.coroutine
	def __calculate_sub_rating(self, data_type, session_dat, user_dat):
		"""Calculate trust score for specific subtype of user data"""
		sub_tot = 0
		if data_type in user_dat and data_type in session_dat:
			for h in user_dat[data_type]: #loop through all the user's hashed data of this type and compare it to the session
				temp = (yield self.__comparers[data_type](
					session_dat[data_type], h))
				if temp > sub_tot: #fix security issue created by the previous ability to combine multiple low scores
					sub_tot = temp
		elif data_type not in session_dat and data_type in user_dat: #the user's session data may have expired or have not been collected
			sub_tot = -1*self.__maxscores[data_type] #score nonexistant data negativly
		return(sub_tot)

	@gen.coroutine
	def get_trust(self, session_dat, user_dat):
		"""This scores how trustworthy a user is as a number between -1 and 1.
		The score is based on how much session data matches the data stored in their user profile.
		A score of 1 means that the user is perfectly trustworthy, a score of 0 means they cannot be trusted.
		A negative score means that the session data has expired and an accurate determination cannot be made.
		"""
		total = 0 #total user score
		actmax = 0 #maximum achievable total (A user with this score is PERFECT)
		for dt in self.__comparers.keys(): #loop through all the types of data
			actmax += self.__maxscores[dt] #add data type's max score to the maximum
			total += yield self.__calculate_sub_rating(dt, session_dat, user_dat) #calculate sub score for given data type and add it to total
		try:
			return(200, str(total/actmax))
		except ZeroDivisionError:
			logger.critical('This server does not have any client data collection and analysis modules installed')
			return(501, 'This server does not have any data collection and analysis modules installed')
