# Bender Lab Animal Colony Database Management System - GUIs

import sys
import os
import debug
import sqlite3 as lite
import Tkinter as tk
import Pmw as p
import tkshortcuts as tksc
import database as acdb


class Gui(tk.Frame):
	"""Generic GUI, to be fitted to a root window.
	Root window fitting should occur in the main script, or should
	be defined in __init__ of classes derived from Gui.

	initial parameters include parent (root), dimensions, and title
	"""

	def __init__(self, master=None, w=350, h=225, title='GUI', background='white'):

# 		save parameters for use in derived classes
# 		(create root first if not specified)
		self.root = master
		if not self.root:
			self.root = tk.Tk()

		self.w = w
		self.h = h

# 		geometry of root, stick frame into specified root and make UI
		self.root.geometry(str(w)+'x'+str(h))
		self.frame = tk.Frame(self.root, background=background)
		self.frame.grid(row=0, column=0, sticky="nsew")
		self.make_ui(title)

	def make_ui(self, title):

# 		ridiculously simple base UI
		self.root.title(title)

class UVW(Gui):
	"""User Verification Window (UVW), from Gui class.
	Asks for username/password before allowing ability to 
	view/export/add/update database information.
	default database is bender_acdb.db
	"""

	def __init__(self, db='bender_acdb.db', text="powered by python"):

# 		start with basics from Gui (no root specified 
# 		so automatically created and filled with a frame)
		Gui.__init__(self, title="ACDBMS -- User Verification Window")
		self.db = db
		self.tick = 0
		self.default_text = text

	def __str__(self):
		return "Bender Lab Animal Colony Database Management System\nUser Verification Window"

	def make_ui(self, title):
		self.root.title(title)

		self.title1 = tk.Label(self.frame, text="BENDER LAB ANIMAL COLONY")
		self.title1.grid(row=0, column=0, columnspan=2, pady=5, padx=5)
		self.title2 = tk.Label(self.frame, text="DATABASE MANAGEMENT SYSTEM")
		self.title2.grid(row=1, column=0, columnspan=2, pady=5, padx=5)

		# Load the UCSF logo and display it on our login screen
		import base64 as b64
		import urllib
		URL = "http://i.imgur.com/QipJK26.gif"
		u = urllib.urlopen(URL)
		raw_data = u.read()
		u.close()
		if debug.level > 0: print "image fetched, len=", len(raw_data)
		image = tk.PhotoImage(data=b64.encodestring(raw_data))
		self.image = tk.Label(self.frame, image=image)
		# store a reference to the image to prevent it going out of scope when we return
		self.image.img = image
		self.image.grid(row=0, column=2, rowspan=5)

		self.un_frame, self.un_entry = tksc.make_entry(frame=self.frame, caption="Username : ")
		self.un_frame.grid(row=2, column=0, columnspan=2, pady=2, padx=5)
		self.un_entry.bind("<Return>", self.entry_callback)

		self.pw_frame, self.pw_entry = tksc.make_entry(frame=self.frame, caption="Password : ", show="*")
		self.pw_frame.grid(row=3, column=0, columnspan=2, pady=2, padx=5)
		self.pw_entry.bind("<Return>", self.entry_callback)

		self.button = tk.Button(self.frame, text="ENTER", command=self.authenticate)
		self.button.grid(row=4, column=0, columnspan=2, pady=5, padx=5)

		self.qb = tk.Button(self.frame, text="QUIT", command=self.quit_uvw)
		self.qb.grid(row=0, column=2, columnspan=2, pady=5, padx=5)

		self.title3 = tk.Label(self.frame, text="powered by python")
		self.title3.grid(row=5, column=0, columnspan=2, pady=3, padx=5)

	def entry_callback(self, event):
		self.authenticate()

	def quit_uvw(self):
		sys.exit()

	def authenticate(self):
# 		retrieve entered username and password and format for the ACDB.get() search
# 		.... return if entries are empty
		un = self.un_entry.get()
		pw = self.pw_entry.get()

		if not un or not pw:
			self.button.config(text="ENTER")
			self.title3.config(text="UN/PW MISSING")
			return

		self.tick += 1
		if debug.level > 0:
			print "AUTHENTICATING, attempt =", self.tick

		search = {}
		search['Username']=un

# 		connect to database and check
		cur = acdb.Colony_cur(self.db, table='Scientists')
		check = cur.get(columns='Password', search_param=search)

		if debug.level > 0:
			try:
				check[0][0]
				print "input :\n\tUsername =", un, "\n\tPassword =", pw, "\n\tdictionary =", check[0][0], '\n'
			except IndexError:
				print "input :\n\tUsername =", un, "\n\tPassword =", pw, "\n\tdictionary = NO CORRESPONDING USERNAME ENTRY\n"

		if check:
			if check[0][0] == pw:
				if debug.level > 0:
					print "Username and Password match! (check[0][0] == pw)\n"
				self.button.config(text="CORRECT")
				self.title3.config(text="CLEARED FOR ACCESS")
				self.tick = 0
				self.user = un
				self.next()
				return

		if debug.level > 0:
			print "Username and Password NOT MATCHED. (check[0][0] != pw)\n"
		self.button.config(text="INCORRECT UN/PW, TRY AGAIN")
		self.title3.config(text=self.default_text)

# 		make sure they're not spamming the system
		if self.tick >= 5:
			if debug.level > 0:
				print "Too many attempts.... pausing for", (self.tick/2.5)**4, "seconds..."
			import time
			self.button.config(command=None)
			self.title3.config(text="TOO MANY ATTEMPTS : TIME OUT")
			time.sleep((self.tick/2.5)**4)
			self.button.config(command=self.authenticate)
			self.title3.config(text=self.default_text)

	def next(self, user=None):
# 		returns error message and does not pass to MAIN if attempts to bypass input of username
		if not user:
			user=self.user
		if debug.level > 0:
			print "user passed to MAIN() =", user, "\n"

# 		initiate MAIN window based on database file specified in UVW (which is specified by RUNME.py)
# 		and the user who accessed it
		main = MAIN(db=self.db, user=self.user)
		self.root.destroy()
		main.root.mainloop()
		print "Finished"

class MAIN(Gui):
	"""Main Colony Acces Window (MAIN), from Gui class.
	Gives access to table manipulation based on tables that user
	has access to as defined in database (current user derived from UVW input) 

	defaults:
		-database is bender_acdb.db
		-user is from UVW()
	"""

	def __init__(self, db='bender_acdb.db', user=None):

# 		protect against attempts to enter without a user specified
		if not user:
			return

# 		see usage in MAIN.make_arcframe()
# 		0's are all entry fields (no search required)
# 		1's are all just more menus
# 		2's need search parameters (so should have search button to display these searches)
		self.fancy_format = {
			'admin': 1,
			'user': 1,
			'mice': 1,
			'scientists': 1,
			'add mice': 0,
			'update mice': 2,
			'search mice': 2,
			'reserve mice': 2,
			'sac mice': 2,
			'add scientists': 0,
			'update scientists': 2,
			'search scientists': 2,
		}

# 		initialize user and authorization level
		self.db = db
		self.user = user
		self.user_class = None

		cur = acdb.Colony_cur(self.db, table='Scientists')
		search = {}
		search['Username']=self.user
		if debug.level > 0:
			print "search_param =", search
		check = cur.get(columns='User_class', search_param=search)
		self.user_class = check[0][0]
		if debug.level > 0:
			print "\ncheck =", check
			print "check[0][0] =", check[0][0]
			print "\nAUTHORIZED USER =", self.user
			print "AUTHORIZATION LEVEL =", self.user_class

# 		start with basics from Gui (no root specified so automatically created and filled with a frame)
		Gui.__init__(self, title="ACDBMS -- Main Window", w=800, h=500, background='wheat')

# 		if bad permissions, get out
		if self.user_class not in ['admin', 'user']:
			print "AUTHORIZATION DENIED FOR USER", self.user
			sys.exit()

	def make_ui(self, title):
# 		this ui's a bit fancier so we're going to initialise Pmw here instead of in default
		p.initialise(self.root)
		self.root.title(title)

# 		determine UI based on self.user_class
		if self.user_class == 'admin':
			self.admin_ui()

		elif self.user_class == 'user':
			self.user_ui()

	def user_ui(self, format='Mice'):

		self.display_title = tk.Label(self.frame, text="DISPLAY")
		self.display_title.grid(row=0, column=0, padx=5, sticky="w")
		
		self.display = tk.Text(self.frame, state=tk.DISABLED)
		self.display.grid(row=1, column=0, padx=5, pady=1, sticky="w")
		self.show("DATA TO BE DISPLAYED HERE")
	
		self.title1 = tk.Label(self.frame, text=("User : %s\nAuthorization Level : %s" % (self.user, self.user_class)))
		self.title1.grid(row=0, column=1, columnspan=2, pady=5, padx=5, sticky="e")
		
		self.arcframe = self.make_arcframe(self.frame, format=format)
		self.arcframe.grid(row=1, column=2, columnspan=2, pady=5, padx=5, sticky="e")
		
		self.qb = tk.Button(self.frame, text="QUIT", command=self.quit_main)
		self.qb.grid(row=2, column=2, pady=2, padx=2, sticky='e')
		
		self.mmb = tk.Button(self.frame, text="MAIN MENU", command=self.return_main)
		self.mmb.grid(row=3, column=2, pady=1, padx=2, sticky='e')
	
	def admin_ui(self):
		self.user_ui(format='admin')
	
	def change_ui(self, selection):
		if debug.level > 0:
			print "\nSTARTING change_ui....\n\tselection =", selection
# 		replace current arcframe with new arcframe based on selection

		if selection.lower() == 'user':
			self.selection = 'Mice'
		else:
			self.selection = selection
		self.new_arcframe = self.make_arcframe(self.frame, format=self.selection)
		
		if self.new_arcframe == None:
			print "ERROR : FORMAT NOT RECOGNIZED"
			self.new_arcframe = 0
			return
		
		self.arcframe.grid_forget()
		self.arcframe = self.new_arcframe
		self.arcframe.grid(row=1, column=1, columnspan=2, pady=5, padx=5)

	def make_arcframe(self, frame, format='admin'):
		"""Establishes an 'arcframe' based on input format.
		Defaults to an admin arcframe, returns None if invalid format
		Note that the Tk() that this frame belongs to must have Pmw initialized.
		"""
		
		data = None
		self.format = format
		if debug.level > 0:
			print "format =", format
		arcframe = tk.Frame(frame)
		self.arc_dict = {} # allows storage of Tkinter objects generated in a loop

#		this arcframe is reserved for admins
		if format.lower() == 'admin':
			label = "What would you like to access?"
			items = ['Mice', 'Scientists']
		
#		this is default for users, accessbile to admins as well
#		allows manipulation of animal colony		
		elif format.lower() == 'mice':
			label = "MOUSE COLONY\nWhat do you want to do?"
			items = ['add mice', 'update mice', 'reserve mice', 'sac mice', 'search mice']

#		this is reserved for admins
#		allows manipulation of researcher information/permissions		
		elif format.lower() == 'scientists':
			label = "RESEARCHER INFORMATION\nWhat do you want to do?"
			items = ['add scientists', 'update scientists', 'search scientists']

		elif self.fancy_format[format.lower()] in [0,2]:
			l_format = format.split()
			label = format.upper()

			if l_format[0].lower() in ['add', 'update', 'search', 'reserve', 'sac']:
				if l_format[1].lower() == 'mice':
					data = ['Gender', 'Genotype', 'Dob', 'Sac', 'Reservation', 'Virus', 'Notes']
				elif l_format[1].lower() == 'scientists':
					data = ['Username', 'Password', 'User_class', 'Type', 'Name', 'Email']

			label = format.upper()

		else:
			print "ERROR : INVALID FORMAT KEY"
			return None
			
# 		store data var for use in callbacks
		if data:
			self.data = data
		if debug.level > 0:
			print "format is now =", format
			print "fancy_format value is =", self.fancy_format[format.lower()]
#		FANCY FORMATTING DISPATCH:

#		these are just entry fields (no search required)
		if self.fancy_format[format.lower()]==0:
			self.title1 = tk.Label(arcframe, text=label)
			self.title1.grid(row=0, column=0, columnspan=2, pady=5, padx=5)
			for i in range(len(data)):
				f, self.arc_dict[data[i]+'_entry'] = tksc.make_entry(frame=arcframe, caption=data[i])
				f.grid(row=i+1, column=0, columnspan=2, pady=2, padx=5)
			self.add_button = tk.Button(arcframe, text="ADD", command=self.add_callback)
			self.add_button.grid(row=len(data)+2, column=0, columnspan=1, pady=5, padx=5)
			return arcframe

#		these send to other menus
		elif self.fancy_format[format.lower()]==1:
			self.title1 = tk.Label(arcframe, text=label)
			self.title1.grid(row=0, column=0, columnspan=2, pady=5, padx=5)
			for i in range(len(items)):
				if debug.level > 0:
					print "\ti =", i
					print "\titems[i] =", items[i]
					print "\tself.arc_dict[", items[i], "+'_button']"
				self.arc_dict[items[i]+'_button'] = tk.Button(arcframe, text=items[i], command=lambda i=i: self.change_ui(items[i]))
				self.arc_dict[items[i]+'_button'].grid(row=i+1, column=0, columnspan=2, pady=2, padx=5)
				if debug.level > 0:
					print "\tself.arc_dict =", self.arc_dict
					for i in self.arc_dict:
						print i, "calls to", self.arc_dict[i].cget("command")

			return arcframe

#		these need search parameters
		elif self.fancy_format[format.lower()]==2:
			self.title2 = tk.Label(arcframe, text='SEARCH DATA')
			self.title2.grid(row=1, column=0, columnspan=2, pady=5, padx=5)

			for i in range(len(data)):
				f, self.arc_dict[data[i]+'_s_entry'] = tksc.make_entry(frame=arcframe, caption=data[i])
				f.grid(row=i+2, column=0, columnspan=2, pady=2, padx=5)

			if 'search' not in format:
				self.title3 = tk.Label(arcframe, text='UPDATED DATA')
				self.title3.grid(row=1, column=2, columnspan=2, pady=5, padx=5)			

				for i in range(len(data)):
					f, self.arc_dict[data[i]+'_u_entry'] = tksc.make_entry(frame=arcframe, caption=data[i])
					f.grid(row=i+2, column=2, columnspan=2, pady=2, padx=5)

				self.change_button = tk.Button(arcframe, text=l_format[0].upper(), command=self.change_callback)
				self.change_button.grid(row=len(data)+3, column=2, columnspan=1, pady=5, padx=5)

			else:
				self.change_button = tk.Button(arcframe, text=l_format[0].upper(), command=self.change_callback)
				self.change_button.grid(row=len(data)+2, column=0, columnspan=1, pady=5, padx=5)

			if debug.level > 0:
				print self.arc_dict

			if 'sac' in format:
				self.arc_dict['Sac_u_entry'].insert(0, "SACRIFICED")

			if 'reserve' in format:
				self.arc_dict['Reservation_u_entry'].insert(0, "RESERVED")

		return arcframe

	def show(self, results):
# 		READ-ONLY
		self.display.config(state=tk.NORMAL)
		self.display.insert(tk.END, '\n----------------\n')
		self.display.insert(tk.END, results)
		self.display.config(state=tk.DISABLED)

# 	callbacks
	def quit_main(self):
		sys.exit()

	def return_main(self):
		self.change_ui(self.user_class)

	def add_callback(self):
# 		prepare data_dict
		dict = {}
		for i in self.data:
# 			in case of incomplete filling in
			if i == '':
				self.title1.config(text='MUST PROVIDE DATA FOR ALL CELLS, TRY AGAIN')
				return
# 			this makes it so empty entries are emitted
			if not self.arc_dict[i+'_entry'].get() == '':
				dict[i] = self.arc_dict[i+'_entry'].get()

		cur = acdb.Colony_cur()

		if 'mice' in self.format:
			result = cur.add_mouse(data_dict=dict)
		elif 'scientists' in self.format:
			result = cur.add_sci(data_dict=dict)

		self.title1.config(text='SUCCESSFULLY ADDED TO DATABASE\nCONTINUE OR RETURN TO MAIN MENU')
		if debug.level > 0:
			print result[0]
		self.show(result[0])

	def change_callback(self):
# 		prepare data_dict and search_param
		dd = {}

# 		default do not search for sac'd or reserved mice
		if 'mice' in self.format:
			sp = {'Sac': 'no', 'Reservation': 'none'}
		else:
			sp = {}

		for i in self.data:
			if i == '':
				self.title1.config(text='MUST PROVIDE DATA FOR ALL CELLS, TRY AGAIN')
				return
			if 'search' not in self.format:
				if not self.arc_dict[i+'_u_entry'].get() == '':
					dd[i] = self.arc_dict[i+'_u_entry'].get()
			if not self.arc_dict[i+'_s_entry'].get() == '':
				sp[i] = self.arc_dict[i+'_s_entry'].get()

# 		dispatch
		if debug.level > 0:
			print "self.format =", self.format
		if 'search' in self.format:
			if 'mice' in self.format:
				cur = acdb.Colony_cur()
				result = cur.get(search_param=sp)
			elif 'scientists' in self.format:
				cur = acdb.Colony_cur(table='Scientists')
				result = cur.get(table='Scientists', search_param=sp)
		else:
			if 'mice' in self.format:
				cur = acdb.Colony_cur()
				result = cur.update_mouse(data_dict=dd, search_param=sp)
			elif 'scientists' in self.format:
				print "here!"
				cur = acdb.Colony_cur(table='Scientists')
				result = cur.update_sci(data_dict=dd, search_param=sp)

		self.title1.config(text='SUCCESSFULLY CHANGED IN DATABASE\nCONTINUE OR RETURN TO MAIN MENU')
		if debug.level > 0:
			print result
		self.show(tuple(cur.col_list))
		for i in range(len(result)):
			if debug.level > 0:
				print i
				print result[i]
			self.show(result[i])