# /usr/bin/python

import hashlib
import random
import datetime
import db
from config import EXPIRATION_PERIOD, DATABASE_PATH, FAILED_LOGIN_TOLERANCE, LOCKOUT_PERIOD
from utils import *

class User(object):
	"""
	This class represents a User with it's state being propagated to the database.

	# Registration
	A user can be registered and must be activated with the appropriate key. Otherwise
	the account will expire. The registration will be generated by the SHA1 hash of
	the username and some random salt to prevent forgery. The method for sending the
	key to the user is merely a mock up and would need to be implemented properly
	for real-world applications.

	# Authentication
	The user can login with his credentials. The user's password will not be stored as
	clear text but it's SHA1 hash. Upon login the supplied password's SHA1 hash will be
	compared to the value stored in the database. If they match the user is logged in.
	Successive failing login attempts will lock the account for a specified amount of time.

	# Authorization
	A user is a member of an authentication group. The most simplistic approach is to have
	two such groups, namely 'admins' and 'non-admins'. Thereby when a user tries to access
	a certain resource his permission can be determined by calling isAdmin() to decide
	whether he is allowed to or not.
	"""

	def __init__(self, username, password, email, authgroup, activated=None, expired=None, logged_in=None, failed_logins=None, locked=None, registration_key=None, key_expiration=None, locked_until=None):
		"""
		Constructor either receiving all user parameter or only basic values
		and generating defaults for the rest.
		"""
		# set the user's details and credentials
		self.username = username
		self.email = email
		self.password = password
		self.authgroup = authgroup
		if activated == None:
			self.activated = False
		else:
			self.activated = activated
		if expired == None:
			self.expired = False
		else:
			self.expired = expired
		if logged_in == None:
			self.logged_in = False
		else:
			self.logged_in = logged_in
		if failed_logins == None:
			self.failed_logins = 0
		else:
			self.failed_logins = failed_logins
		if locked == None:
			self.locked = False
		else:
			self.locked = locked
		if registration_key == None:
			self.registration_key = registrationKey(username)
			newUser = True
		else:
			self.registration_key = registration_key
			newUser = False
		if key_expiration == None:
			self.key_expiration = datetime.datetime.now() + EXPIRATION_PERIOD
		else:
			self.key_expiration = key_expiration
		if locked_until == None:
			# if locked_until is not supplied it's valued doesn't matter
			# so it just should be set to a correct date value
			self.locked_until = datetime.datetime.now()
		else:
			self.locked_until = locked_until

		# if newUser is True the registration key needs to be transmitted to
		# the user's email and his password will be hashed instead of
		# storing it as real text
		if newUser:
			self.password = sha1Hash(self.password)
			self.sendActivationEmail()


	def save(self):
		"""
		Initially saves a new user to the db.
		"""
		dbmanager = db.DBManager()
		if dbmanager.userExists(self.username):
			raise UserExistsException(self.username)
		else:
			dbmanager.insertUser(self)

	def update(self):
		"""
		Propagates the current user state to the database via
		the database manager.
		"""
		# get the manager
		dbmanager = db.DBManager()
		# and to update
		dbmanager.updateUser(self)
		return True

	def activate(self, suppliedkey, now = datetime.datetime.now()):
		"""
		Activates a user if the supplied activation key is correct and
		the point in time is within the expiration period. If the key is
		incorrect the user will not be activated. If the expiration date
		is crossed the user will be expired.
		Returns True if activation is in time and the key is correct,
		False otherwise.
		"""
		# variable to store success of the activation attempt
		isokay = False
		if inTime(now, self.key_expiration):
			if suppliedkey == self.registration_key:
				self.activated = True
				# success
				isokay = not isokay
		else:
			self.expired = True
		# update user object
		self.update()
		return isokay

	def login(self, email, password):
		"""
		Login the user. The user's email and the password must both
		be provided as further security measure in case two user's
		incidentally share the same password.
		If values are correct the user will be logged in until he
		logs out. If the password is wrong the failed attempts will
		be counted and finally result in a locked account for some
		time. A correct login will reset the failed counter. It will
		also be reset when being locked out. The user will stay
		logged in until logout() is called.
		"""
		# check if the correct user is meant at all
		# or if the user is already logged in
		if email != self.email:
			# don't proceed in these cases
			return False
		# is the user allowed to login?
		if self.isLocked():
			# if not, don't proceed
			return False
		# otherwise check the password's credibility
		if sha1Hash(password) == self.password:
			# correct login
			self.logged_in = True
			self.failed_logins = 0
		else:
			# incorrect login
			self.logged_in = False
			self.failed_logins += 1
			if self.failed_logins > FAILED_LOGIN_TOLERANCE:
				self.locked = True
				self.locked_until = datetime.datetime.now() + LOCKOUT_PERIOD
		# propagate to db
		self.update()

	def logout(self):
		"""
		Logout the user.
		"""
		# this operation is not as critical so no checks are done
		self.logged_in = False
		self.update()

	def isActive(self):
		"""
		Returns True if this accoutn has yet been activated, False otherwise.
		"""
		return self.activated

	def isLocked(self):
		"""
		Checks whether a user is locked by considering his
		locked status with respect to the lockout period.
		"""
		# if he's locked out
		if self.locked:
			# check whether the lockout period is still active
			now = datetime.datetime.now()
			if now > self.locked_until:
				# lockout period is over
				# unlock
				self.locked = False
		return self.locked

	def isLoggedIn(self):
		"""
		Returns True if the user is logged in at this moment, False otherwise.
		"""
		return self.logged_in

	def isExpired(self):
		"""
		Returns True if this account has expired, False otherwise.
		"""
		return self.expired

	def isAdmin(self):
		"""
		Determin whether this user is an admin or not.
		Returns True for admin otherwise False.
		"""
		if self.authgroup == 'admin':
			return True
		return False

	def sendActivationEmail(self):
		"""
		Transmit the registration key to the user's email adress.
		"""
		print 'sending registration_key: %s to %s' % (self.registration_key, self.email)
