#!/usr/bin/env python

import sys, MySQLdb, getpass, re#, pexpect
from getopt import getopt
from tools import *

db_struct = 'TABLES.txt'
ini_file = 'mpmdb.ini'
#tbs_del = ['requests']
tbs_no_del = ['users']
db_struct = os.path.join(os.path.split(os.path.abspath(__file__))[0], db_struct)
ini_file = os.path.join(os.path.split(os.path.abspath(__file__))[0], ini_file)

tables = { # not used any more. It is replaced by the file named by db_struct
	'project' : '''(
		id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		name TINYTEXT,
		keywords TINYTEXT,
		factors TINYTEXT,
		tissue TINYTEXT,
		design TINYTEXT,
		QC TINYTEXT,
		description TEXT,
		authors TEXT,
		journal TINYTEXT,
		publish_year YEAR,
		pubmed_id TINYTEXT,
		data_link TEXT,
		INDEX nm (name(50)),
		INDEX kw (keywords(50)),
		INDEX au (authors(50)),
		INDEX tis (tissue(50))
		)''',

	'fileinfo' : '''(
		id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		name TINYTEXT,
		location TINYTEXT,
		category TINYTEXT,
		format TINYTEXT,
		INDEX nm (name(50))
		)''',
	
	'filexref' : '''(
		file_id MEDIUMINT UNSIGNED,
		tb_id MEDIUMINT UNSIGNED,
		tbname char(24),
		INDEX nm (tbname),
		INDEX fl (file_id),
		INDEX tb (tb_id)
		)''',
	
	'platform' : '''(
		id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		name TINYTEXT,
		category TINYTEXT,
		probe_num MEDIUMINT,
		duplicate TINYINT DEFAULT 1,
		manufacturer TINYTEXT,
		organism TINYTEXT,
		description TEXT,
		INDEX nm (name(50))
		)''',
	
	'array' : '''(
		id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		platform_id SMALLINT UNSIGNED,
		channel_num TINYINT,
		hyb_date DATE,
		prot_hyb_id SMALLINT UNSIGNED,
		prot_img_id SMALLINT UNSIGNED,
		prot_data_id SMALLINT UNSIGNED,
		identifier TINYTEXT,
		release_date DATE,
		description TEXT,
		INDEX platform (platform_id)
		)''',
	
	'project_array' : '''(
		project_id SMALLINT UNSIGNED,
		array_id MEDIUMINT UNSIGNED,
		INDEX project (project_id),
		INDEX array (array_id)
		)''',
	
	'sample' : '''(
		id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		name TINYTEXT,
		organism TINYTEXT,
		tissue TINYTEXT,
		gender TINYTEXT,
		age float,
		description TEXT,
		provider TINYTEXT,
		state_develop TINYTEXT,
		state_disease TINYTEXT,
		relapse_status TINYTEXT,
		relapse_time FLOAT,
		pre_op_PSA FLOAT,
		patient_id TINYTEXT,
		cell_type TINYTEXT,
		sample_type TINYTEXT,
		GLSN TINYTEXT,
		meta_site TINYTEXT,
		tissue_perc_T FLOAT,
		tissue_perc_S FLOAT,
		tissue_perc_B FLOAT,
		tissue_perc_G FLOAT,
		tissue_perc_SMS FLOAT,
		tissue_perc_NSS FLOAT,
		capsularlnv TINYTEXT,
		SM TINYTEXT,
		TNN_1992 TINYTEXT,
		race TINYTEXT,
		INDEX nm (name(50)),
		INDEX tis (tissue(50)),
		INDEX ag (age)
		)''',
	
	'sampxref' : '''(
		sample_id MEDIUMINT UNSIGNED,
		array_id MEDIUMINT UNSIGNED,
		channel_No TINYINT,
		dye TINYTEXT,
		prot_proc_id SMALLINT UNSIGNED,
		prot_tech_id SMALLINT UNSIGNED,
		prot_label_id SMALLINT UNSIGNED,
		exp_factor TINYTEXT,
		INDEX sample (sample_id),
		INDEX array (array_id)
		)''',
	
	'protocol' : '''(
		id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		name TINYTEXT,
		category TINYTEXT,
		description TEXT,
		INDEX nm (name(50))
		)''',
	
	'intensity' : '''(
		id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		array_id MEDIUMINT UNSIGNED,
		probe_id INT UNSIGNED,
		channel_No TINYINT,
		fg FLOAT,
		bg FLOAT,
		flag FLOAT,
		INDEX array (array_id)
		)''',
	
	'probe' : '''(
		id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		platform_id SMALLINT UNSIGNED,
		idx MEDIUMINT UNSIGNED,
		block_row SMALLINT UNSIGNED,
		block_col SMALLINT UNSIGNED,
		row SMALLINT UNSIGNED,
		col SMALLINT UNSIGNED,
		gene_symbol TINYTEXT,
		gene_title TINYTEXT,
		chromosome TINYTEXT,
		chr_start INT,
		chr_end INT,
		sequence TEXT,
		bioseq_type TINYTEXT,
		probe_purpose TINYTEXT,
		designation TINYTEXT,
		unique_id TINYTEXT,
		webarray_id INT UNSIGNED,
		INDEX platform (platform_id),
		INDEX unid (unique_id(128)),
		INDEX gsym (gene_symbol(50)),
		INDEX gtitle (gene_title(50)),
		INDEX webarray (webarray_id)
		)''',

	'dbxref' : '''(
		id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
		kw VARCHAR(255),
		dbname VARCHAR(55),
		INDEX dbkw (dbname, kw(50)),
		INDEX ikw (kw(50))
		)''',

	'probe_dbxref' : '''(
		probe_id INT UNSIGNED,
		dbxref_id BIGINT UNSIGNED,
		INDEX probe (probe_id),
		INDEX dbxref (dbxref_id)
		)'''
}
		
def mkBase(tables={}, host='localhost', port=3306, root_name='', root_passwd='', basename='webarray', user='webarray', passwd='webarray_password', mkdb_only=False, GO=None):
	if root_passwd:
		db = MySQLdb.connect(host=host, user=root_name, passwd=root_passwd)
	else:
		db = MySQLdb.connect(host=host, user=root_name)
	cur = db.cursor()
	from sets import Set
	if cur.execute('show databases'):
		bases = [((type(a[0]) is Set) and list(a[0])[0] or a[0] ) for a in cur.fetchall()]
	else: bases = []
	if True or basename not in bases: 
		cur.execute('CREATE DATABASE IF NOT EXISTS %s' % basename)
		if True: #not mkdb_only:
			# to user at any location (host): username; at localhost: username@localhost; at other location: username@ 
			cur.execute('GRANT SELECT on %s.* to %s IDENTIFIED BY "%s"' % (basename, user, passwd) )
			#cur.execute('GRANT SELECT on %s.* to %s@%s IDENTIFIED BY "%s"' % (basename, user, host, passwd) )
			cur.execute('GRANT INSERT on %s.* to %s IDENTIFIED BY "%s"' % (basename, user, passwd) )
			#cur.execute('GRANT INSERT on %s.* to %s@%s IDENTIFIED BY "%s"' % (basename, user, host, passwd) )
			cur.execute('GRANT UPDATE on %s.* to %s IDENTIFIED BY "%s"' % (basename, user, passwd) )
			cur.execute('GRANT LOCK TABLES on %s.* to %s IDENTIFIED BY "%s"' % (basename, user, passwd) )
			#cur.execute('GRANT UPDATE on %s.* to %s@%s IDENTIFIED BY "%s"' % (basename, user, host, passwd) )
			#cur.execute('GRANT DELETE on %s.* to %s@%s IDENTIFIED BY "%s"' % (basename, user, host, passwd) )
			cur.execute('UPDATE mysql.user SET File_priv="Y" where User="%s"' % user )
			#cur.execute('UPDATE mysql.user SET File_priv="Y" where User="%s" and Host="%s"' % (user, host) )

		#if os.path.exists(DB_OBJ):
		#	bak = DB_OBJ+'.bak'
		#	if os.path.exists(bak): os.unlink(bak)
		#	os.rename(DB_OBJ, bak)
		#open(DB_OBJ, 'wt').write(encodeStr('''host = '%s'\nport = %d\nuser = '%s'\npasswd = '%s'\ndb = '%s' ''' % (host, port, user, passwd, basename)))

	if GO: cur.execute('GRANT SELECT on %s.* to %s IDENTIFIED BY "%s"' % (GO, user, passwd) )

	cur.execute('USE %s' % basename)
	#if cur.execute('show tables'):
	#	tbs = [a[0] for a in cur.fetchall()]
	#else: tbs = []
		
	for  t, v in tables.items():
		#if t not in tbs:
		try:
			cur.execute('CREATE TABLE IF NOT EXISTS ' + t + ' ' + v)
		except:
			print 'CREATE TABLE IF NOT EXISTS ' + t + ' ' + v
			raise
		#if t in tbs_del:
		if t not in tbs_no_del:
			try: cur.execute('GRANT DELETE on %s.%s to %s IDENTIFIED BY "%s"' % (basename, t, user, passwd) )
			except:
				print 'GRANT DELETE on %s.%s to %s IDENTIFIED BY "%s"' % (basename, t, user, passwd)
				raise

	if 'refdbs' in tables: fillRefdbs(cur=cur, db=basename)

	db.commit()
	cur.close()
	db.close()

def fillRefdbs(cur=None, db=None, fn='DB_ALLOW.txt'):
	import sets
	if cur is None: cur = getCursor()
	if db is None: exec get_dbstr()
	dblines = sets.Set(map(lambda a:a[0].lower(), inquireDB('SELECT name FROM %s.refdbs ORDER BY name' % db, cursor=cur, fetch=True)))
	for line in open(fn).readlines():
		line = line.strip()
		if not line: continue
		items = [unescape(a.strip()) for a in line.split(':')] # name : descript : user_name
		dnm = items[0]
		if not dnm or dnm.lower() in dblines: continue
		colname = '"%s"' % escSql(items[0])
		descript = len(items)>1 and '"%s"' % escSql(items[1]) or 'NULL'
		update_xpf_id = 'yes'
		if len(items) > 2:
			val = items[2].strip().lower()
			if val == 'no': update_xpf_id = 'no'
		uid = 'NULL'
		if len(items) > 3:
			unm = items[3]
			if unm:
				uid = inquireDB('SELECT id FROM %s.users WHERE user_name="%s"' % (db, unm), cursor=cur, fetch=True)
				uid = uid and str(uid[0][0]) or 'NULL'

		cur.execute('INSERT INTO %s.refdbs (user_id, name, description, update_xpf_id) VALUES (%s, %s, %s, %s)' % (db, uid, colname, descript, repr(update_xpf_id)))

def readDbStruct(db_structure):
	tbnm_str = re.compile(r'\[\s*(\w+)\s*\]\s*$')
	comment_str = re.compile(r'#.*$')
	tbs = {}
	tb = None
	f = file(db_structure)
	for line in f:
		line = comment_str.sub('', line).strip()
		if not line: continue
		rlt = tbnm_str.match(line)
		if rlt:
			tbnm = rlt.groups()[0]
			tbs[tbnm] = tb = []
		elif tb is not None: 
			tb.append(line)
	return tbs

def getDbSQL(tbs, pre='(\n', mid=',\n', suf='\n)'):
	rlt = {}
	for k,v in tbs.items():
		if v:
			rlt[k] = pre + mid.join(v) + suf
		#else: del tbs[k]
	return rlt #tbs

def saveDataDir(db=None, data_dir=None, ini_dict=None, fn=ini_file):
	# save 
	if db and data_dir: # update ini_dict
		if not ini_dict: ini_dict = readINI(fn, getGO=True)
		dic = ini_dict.setdefault(db, {})
		dic['data_dir'] = data_dir
	elif not ini_dict: return data_dir

	saveINI(ini_dict, fn)
	return data_dir

def saveGO(GO, fn=ini_file):
	if GO:
		ini_dict = readINI(fn)
		ini_dict['GO'] = {'name' : GO}
		saveINI(ini_dict, fn)

def getDataDir(db, root_name, root_passwd, fn=ini_file):
	#nm = os.tmpnam()
	#import pexpect
	#sqladmin = pexpect.spawn('mysqladmin -u%s -p ' % root_name)
	#sqladmin.expect('.*ssword:')
	#sqladmin.sendline(root_passwd)
	ini_dict = os.path.exists(fn) and readINI(fn, getGO=True) or {}
	dic = ini_dict.setdefault(db, {})
	if dic.has_key('data_dir'): return dic['data_dir']

	try:
		raise
		ln = os.popen('mysqladmin variables -u %s --password=%s | grep datadir' % (root_name, root_passwd), 'r').readline()
		data_dir = ln[ln.find(os.sep):ln.rfind(os.sep)]
		data_dir = os.sep.join([data_dir, db, 'files'])
	except: 
		#data_dir = '/var/lib/WebArrayDB/%s' % db
		data_dir = os.name == 'posix' and '/var/lib/webarraydb/%s' % db or '\\var\\lib\\webarraydb\\%s' % db

	dic['data_dir'] = data_dir
	#open(ini_file, 'w').write('data_dir = "%s"' % data_dir)	
	saveINI(ini_dict, fn)
	return data_dir

def mkDataDir_pexpect(data_dir, user, group):
	import pexpect
	Prompt = '.*#' # for root
	su = pexpect.spawn(os.getenv('SHELL', '/usr/bin/bash'))
	su.expect(Prompt) 
	def mkDir(data_dir):
		pth, nm = os.path.split(data_dir)
		if not os.path.exists(pth): mkDir(pth)
		if os.path.exists(data_dir): return
		su.sendline('mkdir "%s"' % data_dir)
		su.expect(Prompt)
		su.sendline('chmod 775 "%s"' % data_dir)
		su.expect(Prompt)
		su.sendline('chown %s:%s "%s"' % (user, group, data_dir))
		su.expect(Prompt)
	mkDir(data_dir)
	su.close()
	open(ini_file, 'w').write('data_dir = "%s"' % data_dir)

def mkDataDir(data_dir, user, group):
	if not os.path.exists(data_dir): os.makedirs(data_dir)
	if os.name == 'posix': os.system('chmod -R 775 "%s" && chown -R %s:%s "%s"' % (data_dir, user, group, data_dir))
	#os.chmod(data_dir, 0770)
	#os.chown(data_dir, uid, gid)

def mkUserGroup(user, group, members=['apache', 'mysql']):
	pass

def exitUsage(s=None):
	if s: print s, '\n\n'
	print 'Run as root\n\nUsage: %s [-n dbname] [-t tb_definition_file] [-d data_file_directory] [-u MySQL_Manager_Name] [-p] [--mkdb-only] [--GO=GO_dbname]  [dbname]\nnote: use "-p" only if password is needed\n' % os.path.split(sys.argv[0])[1]
	sys.exit(0)

if __name__ == '__main__': 
	exec get_dbstr() #open('DBSRC').read()
	
	# need to know dbname, table definitions
	optlist, args = getopt(sys.argv[1:], 'n:t:u:P:pd:', ['mkdb-only', 'GO='])
	optdict = dict(optlist)

	# get db name first
	if len(args) > 1: exitUsage('Too many args!')
	elif len(args) == 1: 
		if optdict.has_key('-n'): exitUsage('Duplicate database names!')
		db = args[0]
	elif optdict.has_key('-n'): db = optdict['-n']
	else: exitUsage('No database name offered!')

	# get table definition file
	if not optdict.has_key('-t'): exitUsage('No table definition file offered!')
	db_struct = optdict['-t']
		
	# MySQL manager name and password
	if not optdict.get('-u', ''): root_name = getpass.getuser() #exitUsage()
	else: root_name = optdict['-u']
	
	if optdict.has_key('-P'): root_passwd = optdict['-P']
	elif optdict.has_key('-p'): root_passwd = getpass.getpass()
	else: root_passwd = ''

	# get data_file folder
	if optdict.has_key('-d'): data_dir = saveDataDir(db, optdict['-d'])
	else: data_dir = getDataDir(db, root_name, root_passwd)

	if optdict.has_key('--mkdb-only'): mkdb_only = True
	else: mkdb_only = False

	GO = optdict.get('--GO', None)
	if GO: saveGO(GO)

	tables = getDbSQL(readDbStruct(db_struct))
	try:
		# exec get_dbstr() #open('DBSRC').read()
		group = user
		mkBase(root_name = root_name, root_passwd = root_passwd, basename=db, user=user, passwd=passwd, tables=tables, mkdb_only=mkdb_only, GO=GO)
	except:
		print 'You need MySQL manager privilege to create database!\n\n'
		raise
	if not mkdb_only:
		#encodeDB()
		# make user and group
		mkUserGroup(user=user, group=group)
		# make data_dir
		#mkDataDir(data_dir, root_name, '')
		mkDataDir(data_dir, 'apache', 'root')
